bond_order_processing.input_data
1__docformat__ = "google" 2"""Input data module.""" 3from abc import ABC 4from abc import abstractmethod 5import re 6from dataclasses import dataclass 7from math import degrees 8from math import acos 9from enum import Enum 10import numpy as np 11from numpy.typing import NDArray 12from typing import TypeAlias 13from typing import Optional 14 15 16x: TypeAlias = float 17"""x-cartesian coordinate""" 18y: TypeAlias = float 19"""y-cartesian coordinate""" 20z: TypeAlias = float 21"""z-cartesian coordinate""" 22vector: TypeAlias = tuple[x, y, z] 23"""euclidean vector""" 24deg: TypeAlias = float 25"""angle in degrees""" 26 27 28class LoadedData(Enum): 29 """Enumeration used by **InputDataFromCPMD**.""" 30 UnitCell = 1 31 MayerBondOrders = 2 32 Populations = 3 33 CoordinatesOfAtoms = 4 34 35 36class Constants: 37 """This class holds constants used in calculations in module 38 **input_data**.""" 39 _CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR: float = 0.52917720859 40 """The length of the bohr radius in angstroms.""" 41 42 43@dataclass 44class UnitCell(Constants): 45 """Unit cell. 46 47 Object represent unit cell. 48 49 Attributes: 50 a (float): Angstroms 51 b (float): Angstroms 52 c (float): Angstroms 53 lattice_vectors (tuple[vector]): Table represents lattice vectors. 54 alfa (deg): deg 55 beta (deg): deg 56 gamma (deg): deg 57 58 """ 59 60 lattice_vectors: tuple[vector] | tuple = () 61 converted_to_angstroms: bool = False 62 63 a: float = 0 64 b: float = 0 65 c: float = 0 66 67 alfa: deg = 0 68 beta: deg = 0 69 gamma: deg = 0 70 71 def calculate_edges_lengths(self) -> tuple[x, y, z] | None: 72 """Calculate edge lengths 73 74 Example: 75 >>> unit_cell = UnitCell() 76 >>> unit_cell.lattice_vectors = ((1, 0, 0),\ 77 (0, 1, 0),\ 78 (0, 0, 1)) 79 >>> unit_cell.calculate_edges_lengths() 80 (1.0, 1.0, 1.0) 81 >>> unit_cell.a 82 1.0 83 84 Returns: 85 **tuple[x, y, z] | None**: To calculate edges lattice_vectors 86 are required else returns None 87 """ 88 if self.lattice_vectors == (): 89 return None 90 else: 91 self.a = (self.lattice_vectors[0][0] ** 2 92 + self.lattice_vectors[0][1] ** 2 93 + self.lattice_vectors[0][2] ** 2) ** (1/2) 94 self.b = (self.lattice_vectors[1][0] ** 2 95 + self.lattice_vectors[1][1] ** 2 96 + self.lattice_vectors[1][2] ** 2) ** (1/2) 97 self.c = (self.lattice_vectors[2][0] ** 2 98 + self.lattice_vectors[2][1] ** 2 99 + self.lattice_vectors[2][2] ** 2) ** (1/2) 100 return (self.a, self.b, self.c) 101 102 def calculate_interaxial_angles(self) -> tuple[deg, deg, deg] | None: 103 """Calculate interaxial angles. 104 105 Example: 106 >>> unit_cell = UnitCell() 107 >>> unit_cell.lattice_vectors = ((1, 1, 0),\ 108 (0, 1, 0),\ 109 (0, 0, 1)) 110 >>> angles = unit_cell.calculate_interaxial_angles() 111 >>> round(angles[0], 1) 112 90.0 113 >>> round(angles[1], 1) 114 90.0 115 >>> round(angles[2], 1) 116 45.0 117 118 Returns: 119 **tuple[deg, deg, deg] | None**: To calculate angles lattice_vectors 120 are required else returns None 121 """ 122 if self.lattice_vectors == (): 123 return None 124 else: 125 if self.a == 0 or self.b == 0 or self.c == 0: 126 self.calculate_edges_lengths() 127 128 self.alfa = degrees(acos(( 129 self.lattice_vectors[1][0] 130 * self.lattice_vectors[2][0] 131 + self.lattice_vectors[1][1] 132 * self.lattice_vectors[2][1] 133 + self.lattice_vectors[1][2] 134 * self.lattice_vectors[2][2]) 135 / (self.b * self.c))) 136 137 self.beta = degrees(acos(( 138 self.lattice_vectors[0][0] 139 * self.lattice_vectors[2][0] 140 + self.lattice_vectors[0][1] 141 * self.lattice_vectors[2][1] 142 + self.lattice_vectors[0][2] 143 * self.lattice_vectors[2][2]) 144 / (self.a * self.c))) 145 146 self.gamma = degrees(acos(( 147 self.lattice_vectors[0][0] 148 * self.lattice_vectors[1][0] 149 + self.lattice_vectors[0][1] 150 * self.lattice_vectors[1][1] 151 + self.lattice_vectors[0][2] 152 * self.lattice_vectors[1][2]) 153 / (self.a * self.b))) 154 155 return (self.alfa, self.beta, self.gamma) 156 157 def convert_cell_data_to_angstroms(self) -> None: 158 """Convert cell data to angstroms. 159 160 You can use it to convert data in lattice_vectors, a, b and c 161 from Bohr units to angstroms. 162 163 Example: 164 >>> unit_cell = UnitCell() 165 >>> unit_cell.lattice_vectors = ((1, 1, 0),\ 166 (0, 1, 0),\ 167 (0, 0, 1)) 168 >>> unit_cell.convert_cell_data_to_angstroms() 169 >>> unit_cell.lattice_vectors 170 ((0.52917720859, 0.52917720859, 0.0), (0.0, 0.52917720859, 0.0), \ 171(0.0, 0.0, 0.52917720859)) 172 173 """ 174 if self.converted_to_angstroms is False: 175 new_lattice_vectors = [] 176 for vector in self.lattice_vectors: 177 new_lattice_vectors.append(( 178 vector[0] * self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR, 179 vector[1] * self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR, 180 vector[2] * self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR)) 181 182 self.lattice_vectors = tuple(new_lattice_vectors) 183 184 if self.a != 0 and self.b != 0 and self.c != 0: 185 self.a = self.a\ 186 * self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR 187 self.b = self.b\ 188 * self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR 189 self.c = self.c\ 190 * self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR 191 192 self.converted_to_angstroms = True 193 194 195class Populations: 196 """Populations from MULLIKEN, LOWDIN analysis.""" 197 198 mulliken: dict[int, tuple[str, float]] = {} 199 lodwin: dict[int, tuple[str, float]] = {} 200 valence: dict[int, tuple[str, float]] = {} 201 202 def __init__(self) -> None: 203 pass 204 205 206class MayerBondOrders: 207 """Object stores Mayer bond orders of atoms pairs.""" 208 rows = list[float] 209 """Type alias.""" 210 mayer_bond_orders: list[rows] = [] 211 horizontal_atom_symbol: dict[int, str] = {} 212 atom_id: list[int] = [] 213 vertical_atom_symbol: dict[int, str] = {} 214 215 def __init__(self, mayer_bond_orders: list[rows], 216 atom_id: list[int], 217 horizontal_atom_symbol: dict[int, str] = {}, 218 vertical_atom_symbol: dict[int, str] = {}): 219 220 if (len(mayer_bond_orders) == len(atom_id) and 221 len(mayer_bond_orders[0]) == len(atom_id)): 222 self.mayer_bond_orders = mayer_bond_orders 223 self.atom_id = atom_id 224 else: 225 raise ValueError("Size of mayer_bond_orders must fit" 226 + "to horizontal_atom_id and " 227 + "vertical_atom_id!!!") 228 229 if (horizontal_atom_symbol != {} and vertical_atom_symbol != {} 230 and len(horizontal_atom_symbol) == len(mayer_bond_orders) 231 and len(vertical_atom_symbol) == len(mayer_bond_orders[0])): 232 self.horizontal_atom_symbol = horizontal_atom_symbol 233 self.vertical_atom_symbol = vertical_atom_symbol 234 elif (horizontal_atom_symbol == {} 235 and vertical_atom_symbol == {}): 236 pass 237 else: 238 raise ValueError("Size of mayer_bond_orders must fit" 239 + "to horizontal_atom_symbol and " 240 + "vertical_atom_symbol!!!") 241 242 def get_mayer_bond_order_between_atoms(self, atom_id_1: int, atom_id_2: 243 int) -> float: 244 """Get mayer bond order between atoms. 245 246 Args: 247 atom_id_1 (int): first atom id (row) 248 atom_id_2 (int): second atom id (column) 249 250 Returns: 251 **float**: bond order 252 """ 253 254 row = self.mayer_bond_orders[self.atom_id.index(atom_id_2)] 255 return row[self.atom_id.index(atom_id_1)] 256 257 def get_atoms_ids(self, atom_symbol: str) -> list[int]: 258 """Get atoms ids in MayerBondOrders. 259 260 Args: 261 atom_symbol (str): Symbol of atom eg. P, Fe, ... 262 263 Returns: 264 **list[int]**: list of ids 265 """ 266 ids = [] 267 for key, value in self.horizontal_atom_symbol.items(): 268 if value == atom_symbol: 269 ids.append(key) 270 271 return ids 272 273 def check_atom_symbol_in_MBO(self, atom_symbol: str) -> bool: 274 """Check atom symbol in MayerBondOrders object. 275 276 Args: 277 atom_symbol (str): Symbol of atom eg. 'P'. 278 279 Returns: 280 **bool**: True if in MayerBondOrders object. 281 282 """ 283 if self.get_atoms_ids(atom_symbol) == []: 284 return False 285 else: 286 return True 287 288 def get_atom_symbols(self, atom_id_1: int, atom_id_2: 289 int) -> tuple[str, str] | None: 290 """Get atom symbols of pair of atoms. 291 292 Args: 293 atom_id_1 (int): atom 1 id 294 atom_id_2 (int): atom 2 id 295 296 Returns: 297 **tuple(str, str) | None**: Symbol of atom 1 and atom 2, 298 if wrong id of atoms returns None or 299 atoms symbols have not been used. 300 301 """ 302 atom_1 = self.horizontal_atom_symbol.get(atom_id_1, None) 303 atom_2 = self.vertical_atom_symbol.get(atom_id_2, None) 304 305 if atom_1 is None or atom_2 is None: 306 return None 307 else: 308 return (atom_1, atom_2) 309 310 def get_all_mayer_bond_orders_of_atom(self, atom_id) -> list[float]: 311 """Returns list of mayer bond orders of atom of given id. 312 313 Args: 314 atom_id (int): atom id. 315 316 Returns: 317 **list[float]**: list of mayer bond orders. 318 """ 319 row = self.mayer_bond_orders[self.atom_id.index(atom_id)] 320 return row 321 322 def get_mayer_bond_orders_list_between_two_atoms(self, atom_symbol_1: str, 323 atom_symbol_2: str 324 ) -> list[float]: 325 """Get Mayer bond orders list between two atoms. 326 327 Args: 328 atom_symbol_1 (str): atom symbol eg. "Fe" 329 atom_symbol_2 (str): atom symbol eg. "Fe" 330 331 Returns: 332 **list[float]**: list of mayer bond orders 333 """ 334 mayer_bond_orders = [] 335 for atom_id_1 in self.atom_id: 336 if atom_symbol_1 == self.vertical_atom_symbol.get(atom_id_1): 337 for atom_id_2 in range(atom_id_1 + 1, len(self.atom_id) + 1): 338 if atom_symbol_2 == self.vertical_atom_symbol.get(atom_id_2): 339 mayer_bond_orders.append( 340 self.get_mayer_bond_order_between_atoms(atom_id_1, 341 atom_id_2)) 342 343 return mayer_bond_orders 344 345 346class CoordinatesOfAtoms(Constants): 347 """Object represents coordinates of atoms. 348 349 Args: 350 atom_coordinates_table (list[tuple[atom_id, str, x, y, z]], optional): \ 351 Table with atom coordinates. 352 353 Attributes: 354 ids (list[atom_id]): ids of atoms. 355 atom_symbols (dict[atom_id, str]): \ 356 Dictionary storing symbols of atoms, key is atom id. 357 358 Types: 359 atom_id = int 360 361 """ 362 363 atom_id: TypeAlias = int 364 """Type alias""" 365 ids: list[atom_id] 366 _coordinates: dict[atom_id, vector] 367 atom_symbols: dict[atom_id, str] 368 _unit_cell: UnitCell | None 369 370 def __init__(self, atom_coordinates_table: list[tuple[atom_id, str, x, y, 371 z]] = []) -> None: 372 373 self.ids = [] 374 self._coordinates = {} 375 self.atom_symbols = {} 376 self._unit_cell = None 377 378 if atom_coordinates_table != []: 379 while atom_coordinates_table != []: 380 row = atom_coordinates_table.pop(0) 381 self.ids.append(row[0]) 382 self.atom_symbols.update({row[0]: row[1]}) 383 self._coordinates.update({row[0]: (row[2], row[3], row[4])}) 384 385 def add_new_atom(self, id: int, atom_symbol: str, coordinates: 386 tuple[x, y, z]) -> None: 387 """Add new atom coordinates to CoordinatesOfAtoms object. 388 389 Args: 390 id (int): id of new atom. 391 atom_symbol (str): Symbol of new atom. 392 coordinates (tuple[x, y, z]): Coordinates of new atom. 393 394 """ 395 self.ids.append(id) 396 self.atom_symbols.update({id: atom_symbol}) 397 self._coordinates.update({id: coordinates}) 398 399 def get_atom_coordinates(self, id: int) -> tuple[x, y, z] | None: 400 """Get atoms coordinates. 401 402 Args: 403 id (int): atom id 404 405 Returns: 406 **tuple[x, y, z] | None**: Returns atom coordinates or None if atom of 407 given id does not exist. 408 409 """ 410 return self._coordinates.get(id, None) 411 412 def get_atom_coordinates_converted_in_angstrom(self, id: int)\ 413 -> tuple[x, y, z] | None: 414 """Get atom coordinates converted in angstroms if they were stored as 415 Bohr units. Not change stored values. 416 417 Example: 418 >>> coordinates_of_atoms = CoordinatesOfAtoms() 419 >>> coordinates_of_atoms.add_new_atom(1, 'P', (1, 1, 1)) 420 >>> coordinates_of_atoms.get_atom_coordinates_converted_in_angstrom(1) 421 (0.52917720859, 0.52917720859, 0.52917720859) 422 423 Args: 424 id (int): atom id 425 426 Returns: 427 **tuple[x, y, z]**: coordinates in angstroms 428 429 """ 430 temp_coordinates = self._coordinates.get(id, None) 431 if temp_coordinates is not None: 432 coordinates = ( 433 self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR 434 * temp_coordinates[0], 435 self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR 436 * temp_coordinates[1], 437 self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR 438 * temp_coordinates[2]) 439 return coordinates 440 else: 441 return None 442 443 def convert_stored_coordinates_to_angstroms(self) -> None: 444 """Converts stored coordinates from bohr units to angstroms. 445 446 Example: 447 >>> coordinates_of_atoms = CoordinatesOfAtoms() 448 >>> coordinates_of_atoms.add_new_atom(1, 'P', (1, 1, 1)) 449 >>> coordinates_of_atoms.add_new_atom(2, 'P', (0, 0, 0)) 450 >>> coordinates_of_atoms.convert_stored_coordinates_to_angstroms() 451 >>> coordinates_of_atoms.get_atom_coordinates(1) 452 (0.52917720859, 0.52917720859, 0.52917720859) 453 454 """ 455 456 for id, coord in self._coordinates.items(): 457 self._coordinates[id] = ( 458 self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR * coord[0], 459 self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR * coord[1], 460 self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR * coord[2], 461 ) 462 463 def get_atom_symbol(self, id: int) -> str | None: 464 """Get atom symbol 465 466 Args: 467 id (int): atom id 468 469 Returns: 470 **Union[str, None]**: Returns atom symbol or None if atom of 471 given id does not exist. 472 473 """ 474 return self.atom_symbols.get(id) 475 476 @property 477 def coordinates(self) -> list[vector]: 478 coordinates = [value for value in self._coordinates.values()] 479 return coordinates 480 481 def add_unit_cell(self, unit_cell: UnitCell): 482 self._unit_cell = unit_cell 483 484 def remove_unit_cell(self): 485 self._unit_cell = None 486 487 def get_distance_between_atoms(self, atom_id_1: int, atom_id_2: int)\ 488 -> float | None: 489 """ Calculate distance between two atoms. 490 491 Args: 492 atom_id_1 (int): id of first atom 493 atom_id_2 (int): id of second atom 494 Returns: 495 **float | None**: Distance in angstroms or Bohr units. 496 497 """ 498 499 if type(self._unit_cell) is not UnitCell: 500 raise Exception("Unit cell not added or wrong type of" 501 + " unit_cell, use .add_unit_cell(self, unit_cell) method!!!") 502 503 atom_coord_1_v = self._coordinates.get(atom_id_1, None) 504 atom_coord_2_v = self._coordinates.get(atom_id_2, None) 505 506 if atom_coord_1_v is None or atom_coord_2_v is None: 507 return None 508 else: 509 atom_coord_1 = np.array(atom_coord_1_v) 510 atom_coord_2 = np.array(atom_coord_2_v) 511 512 vector_between_atoms = atom_coord_1 - atom_coord_2 513 514 length = float(np.linalg.norm(vector_between_atoms)) 515 516 lattice_vectors_list = [] 517 for vector in self._unit_cell.lattice_vectors: 518 lattice_vectors_list.append(np.array(vector)) 519 520 def get_lower_distance(length: float, vector_1: NDArray, 521 vector_2: NDArray) -> float: 522 vector_between_atoms = vector_1 - vector_2 523 524 new_length = float(np.linalg.norm(vector_between_atoms)) 525 526 if new_length < length: 527 length = new_length 528 529 return length 530 531 for vector in lattice_vectors_list: 532 atom_2_coord_in_neighboring_unit = atom_coord_2 + vector 533 534 length = get_lower_distance(length, atom_coord_1, 535 atom_2_coord_in_neighboring_unit) 536 537 for vector in lattice_vectors_list: 538 atom_2_coord_in_neighboring_unit = atom_coord_2 - vector 539 540 length = get_lower_distance(length, atom_coord_1, 541 atom_2_coord_in_neighboring_unit) 542 543 for i in range(3): 544 for j in range(i + 1, 3): 545 translation = lattice_vectors_list[i] + lattice_vectors_list[j] 546 atom_2_coord_in_neighboring_unit = atom_coord_2 + translation 547 548 length = get_lower_distance(length, atom_coord_1, 549 atom_2_coord_in_neighboring_unit) 550 551 atom_2_coord_in_neighboring_unit = atom_coord_2 - translation 552 553 length = get_lower_distance(length, atom_coord_1, 554 atom_2_coord_in_neighboring_unit) 555 556 translation = lattice_vectors_list[i] - lattice_vectors_list[j] 557 atom_2_coord_in_neighboring_unit = atom_coord_2 + translation 558 559 length = get_lower_distance(length, atom_coord_1, 560 atom_2_coord_in_neighboring_unit) 561 562 atom_2_coord_in_neighboring_unit = atom_coord_2 - translation 563 564 length = get_lower_distance(length, atom_coord_1, 565 atom_2_coord_in_neighboring_unit) 566 567 translation = lattice_vectors_list[0] + lattice_vectors_list[1]\ 568 + lattice_vectors_list[2] 569 570 atom_2_coord_in_neighboring_unit = atom_coord_2 + translation 571 572 length = get_lower_distance(length, atom_coord_1, 573 atom_2_coord_in_neighboring_unit) 574 575 atom_2_coord_in_neighboring_unit = atom_coord_2 - translation 576 577 length = get_lower_distance(length, atom_coord_1, 578 atom_2_coord_in_neighboring_unit) 579 580 translation = lattice_vectors_list[0] - lattice_vectors_list[1]\ 581 + lattice_vectors_list[2] 582 583 atom_2_coord_in_neighboring_unit = atom_coord_2 + translation 584 585 length = get_lower_distance(length, atom_coord_1, 586 atom_2_coord_in_neighboring_unit) 587 588 atom_2_coord_in_neighboring_unit = atom_coord_2 - translation 589 590 length = get_lower_distance(length, atom_coord_1, 591 atom_2_coord_in_neighboring_unit) 592 593 translation = lattice_vectors_list[0] - lattice_vectors_list[1]\ 594 - lattice_vectors_list[2] 595 596 atom_2_coord_in_neighboring_unit = atom_coord_2 + translation 597 598 length = get_lower_distance(length, atom_coord_1, 599 atom_2_coord_in_neighboring_unit) 600 601 atom_2_coord_in_neighboring_unit = atom_coord_2 - translation 602 603 length = get_lower_distance(length, atom_coord_1, 604 atom_2_coord_in_neighboring_unit) 605 606 translation = lattice_vectors_list[0] + lattice_vectors_list[1]\ 607 - lattice_vectors_list[2] 608 609 atom_2_coord_in_neighboring_unit = atom_coord_2 + translation 610 611 length = get_lower_distance(length, atom_coord_1, 612 atom_2_coord_in_neighboring_unit) 613 614 atom_2_coord_in_neighboring_unit = atom_coord_2 - translation 615 616 length = get_lower_distance(length, atom_coord_1, 617 atom_2_coord_in_neighboring_unit) 618 619 return length 620 621 622class InputData(ABC): 623 """Input data. 624 625 Load data from file and create objects. 626 627 """ 628 629 populations: Populations | None = None 630 unit_cell: UnitCell | None = None 631 mayer_bond_orders: MayerBondOrders | None = None 632 coordinates_of_atoms: CoordinatesOfAtoms | None = None 633 634 def __init__(self, Populations: type = Populations, 635 UnitCell: type = UnitCell, 636 MayerBondOrders: type = MayerBondOrders, 637 CoordinatesOfAtoms: type = CoordinatesOfAtoms): 638 """Constructor. 639 640 Args: 641 Populations (type, optional): Defaults to Populations. 642 UnitCell (type, optional): Defaults to UnitCell. 643 MayerBondOrders (type, optional): Defaults to MayerBondOrders. 644 CoordinatesOfAtoms (type, optional): Defaults to CoordinatesOfAtoms. 645 646 """ 647 648 self.Populations = Populations 649 self.UnitCell = UnitCell 650 self.MayerBondOrders = MayerBondOrders 651 self.CoordinatesOfAtoms = CoordinatesOfAtoms 652 self.LoadedData = LoadedData 653 654 @abstractmethod 655 def load_input_data(self, path: str, *args) -> None: 656 """Load input data from file. 657 658 Args: 659 path (str): Path to file. 660 *args: LoadData.UnitCell, LoadedData.MayerBondOrders,\ 661 LoadData.Populations or LoadData.CoordinatesOfAtoms 662 663 """ 664 pass 665 666 @staticmethod 667 def check_is_correct_file(data_in_file: str, fingerprint: str) -> bool: 668 if fingerprint in data_in_file: 669 return True 670 else: 671 return False 672 673 674class InputDataFromCPMD(InputData): 675 """Loads data from CPMD file and create objects.""" 676 _fingerprint: str = "The CPMD consortium" 677 _fingerprint_beginning_populations: str = "POPULATION ANALYSIS FROM PROJECTED"\ 678 + " WAVEFUNCTIONS" 679 _fingerprint_end_populations: str = "ChkSum\(POP_MUL\)" 680 _fingerprint_coordinates_of_atoms: str = "ATOM COORDINATES"\ 681 + " CHARGES" 682 _fingerprint_end_coordinates_of_atoms: str = "ChkSum\(CHARGES\)" 683 _fingerprint_mayer_bond_orders: str = "MAYER BOND ORDERS FROM PROJECTED"\ 684 + " WAVEFUNCTIONS" 685 686 _fingerprints_unit_cell: tuple[str, str, str] = ("LATTICE VECTOR A1\(BOHR\):", 687 "LATTICE VECTOR A2\(BOHR\):", 688 "LATTICE VECTOR A3\(BOHR\):") 689 690 _valid_population_columns_names: tuple[str, str, str, str] = ( 691 'ATOM', 'MULLIKEN', 'LOWDIN', 'VALENCE') 692 693 _valid_coordinatios_columns_names: tuple[str, str, str] = ( 694 'X', 'Y', 'Z') 695 696 def load_input_data(self, path: str, *args) -> None: 697 """Loads input data from CPMD file. 698 699 Args: 700 path (str): path to file 701 *args: LoadData.UnitCell, LoadedData.MayerBondOrders, 702 LoadData.Populations or LoadData.CoordinatesOfAtoms 703 704 """ 705 with open(path, 'r') as file: 706 data = file.read() 707 708 if self.check_is_correct_file(data, self._fingerprint) is not True: 709 raise Exception("Wrong input file!") 710 else: 711 if self.LoadedData.UnitCell in args: 712 self.unit_cell = self._load_unit_cell(data) 713 if self.LoadedData.MayerBondOrders in args: 714 self.mayer_bond_orders = \ 715 self._load_mayer_bond_orders(data) 716 if self.LoadedData.Populations in args: 717 self.populations = self._load_populations(data) 718 if self.LoadedData.CoordinatesOfAtoms in args: 719 self.coordinates_of_atoms = \ 720 self._load_coordinates_of_atoms(data) 721 722 def return_data(self, loaded_data: LoadedData)\ 723 -> Populations | UnitCell | MayerBondOrders | CoordinatesOfAtoms\ 724 | None: 725 """ Returns loaded data. 726 727 Args: 728 729 Returns: 730 **Populations | UnitCell | MayerBondOrders | CoordinatesOfAtoms**: 731 732 """ 733 if loaded_data is LoadedData.UnitCell: 734 return self.unit_cell 735 elif loaded_data is LoadedData.MayerBondOrders: 736 return self.mayer_bond_orders 737 elif loaded_data is LoadedData.Populations: 738 return self.populations 739 elif loaded_data is LoadedData.CoordinatesOfAtoms: 740 return self.coordinates_of_atoms 741 else: 742 raise TypeError("Type is not LoadedData!!!!") 743 744 def _load_populations(self, file: str) -> Populations | None: 745 746 rows = self._get_rows_of_data_from_file(file, 747 self._fingerprint_beginning_populations, 748 self._fingerprint_end_populations) 749 labels: list = [] 750 if rows is not None: 751 for row in rows: 752 have_string = True if re.search('[a-zA-Z]+', row) is \ 753 not None else False 754 755 have_number = True if re.search('[0-9]+', row) is \ 756 not None else False 757 758 if have_string and not have_number: 759 labels = row.split() 760 populations = Populations() 761 continue 762 elif have_string and have_number: 763 splited = row.split() 764 if len(labels) + 1 != len(splited): 765 raise Exception( 766 'Wrong quantity of columns in input file!') 767 else: 768 populations = self._add_populations_attributes( 769 populations, splited, labels) 770 return populations 771 else: 772 return None 773 774 @classmethod 775 def _add_populations_attributes(cls, populations: Populations, 776 splited: list, labels: list[str])\ 777 -> Populations: 778 i = 0 779 for item in splited: 780 if i == 0: 781 id = int(item) 782 elif labels[i-1] == cls._valid_population_columns_names[0]: 783 symbol = item 784 elif labels[i-1] == cls._valid_population_columns_names[1]: 785 mulliken_value = float(item) 786 elif labels[i-1] == cls._valid_population_columns_names[2]: 787 lowdin_value = float(item) 788 elif labels[i-1] == cls._valid_population_columns_names[3]: 789 valence_value = float(item) 790 i += 1 791 792 populations.lodwin.update({id: (symbol, lowdin_value)}) 793 populations.mulliken.update({id: (symbol, mulliken_value)}) 794 populations.valence.update({id: (symbol, valence_value)}) 795 796 return populations 797 798 def _load_coordinates_of_atoms(self, file: str)\ 799 -> CoordinatesOfAtoms | None: 800 rows = self._get_rows_of_data_from_file(file, 801 self._fingerprint_coordinates_of_atoms, 802 self._fingerprint_end_coordinates_of_atoms) 803 if rows is not None: 804 for row in rows: 805 have_string = True if re.search('[a-zA-Z]+', row) is \ 806 not None else False 807 808 have_number = True if re.search('[0-9]+', row) is \ 809 not None else False 810 811 if have_string and not have_number: 812 labels = row.split() 813 coordinates_of_atoms = self.CoordinatesOfAtoms() 814 continue 815 elif have_string and have_number: 816 splited = row.split() 817 if len(labels) + 2 != len(splited): 818 raise Exception( 819 'Wrong quantity of columns in input file!') 820 else: 821 coordinates_of_atoms = self._add_coordinations_attributes( 822 coordinates_of_atoms, splited, labels 823 ) 824 return coordinates_of_atoms 825 else: 826 return None 827 828 @classmethod 829 def _add_coordinations_attributes(cls, coordinates_of_atoms: 830 CoordinatesOfAtoms, 831 splited: list, labels: list[str])\ 832 -> CoordinatesOfAtoms: 833 i = 0 834 for item in splited: 835 if i == 0: 836 atom_id = int(item) 837 elif i == 1: 838 atom_symbol = item 839 elif labels[i - 2] == cls._valid_coordinatios_columns_names[0]: 840 x = float(item) 841 elif labels[i - 2] == cls._valid_coordinatios_columns_names[1]: 842 y = float(item) 843 elif labels[i - 2] == cls._valid_coordinatios_columns_names[2]: 844 z = float(item) 845 i += 1 846 847 coordinates_of_atoms.add_new_atom(atom_id, atom_symbol, (x, y, z)) 848 return coordinates_of_atoms 849 850 @staticmethod 851 def _get_rows_of_data_from_file(file: str, 852 finger_print_begin: str, finger_print_end)\ 853 -> list[str] | None: 854 """Get rows fo data from file 855 856 Args: 857 finger_print_begin (str): fingerprint on beginning of data 858 finger_print_end (str): fingerprint on end of data 859 860 Returns: 861 **list[str]**: rows of string data 862 863 """ 864 865 regex = f'(?<={finger_print_begin})[\s\S]*' \ 866 + f'(?={finger_print_end})' 867 868 match = re.search(regex, file) 869 if match is not None: 870 return match[0].split('\n') 871 else: 872 return None 873 874 def _load_mayer_bond_orders(self, file: str) -> MayerBondOrders: 875 match = re.search( 876 f"(?<={self._fingerprint_mayer_bond_orders}\n\n)([\S ]+\n)*", file) 877 if match is None: 878 raise Exception("Mayers bond orders aren't in file or wrong file" 879 + "format!!!!") 880 rows = match[0].split('\n') 881 rows = [row for row in rows if row != ''] 882 number_of_atoms = int(rows[-1].split()[0]) 883 n = number_of_atoms // 8 884 m = 1 if number_of_atoms % 8 > 0 else 0 885 number_of_tables = m + n 886 887 number_of_rows = (len(rows)) * number_of_tables 888 match = re.search( 889 f"(?<={self._fingerprint_mayer_bond_orders}\n\n)([\S ]+\n*){{{number_of_rows}}}", file) 890 891 if match is None: 892 raise Exception("Mayers bond orders aren't in file or wrong file" 893 + "format!!!!") 894 rows_full_table = [] 895 tables = match[0].split('\n\n') 896 first_table = True 897 for table in tables: 898 splited = table.split('\n') 899 i = 0 900 for item in splited: 901 if first_table: 902 row = item.split() 903 rows_full_table.append(row) 904 elif i == 0: 905 row = item.split() 906 rows_full_table[i].extend(row) 907 else: 908 row = item.split() 909 rows_full_table[i].extend(row[2:]) 910 i += 1 911 first_table = False 912 913 row_horizontal = rows_full_table.pop(0) 914 i = 0 915 j = 0 916 horizontal_atom_symbol = {} 917 horizontal_atom_id = [] 918 for item in row_horizontal: 919 if i % 2 == 0: 920 j += 1 921 horizontal_atom_id.append(int(item)) 922 else: 923 horizontal_atom_symbol.update( 924 {horizontal_atom_id[j - 1]: str(item)}) 925 i += 1 926 927 vertical_atom_symbol = {} 928 vertical_atom_id = [] 929 new_rows_full_table = [] 930 for row in rows_full_table: 931 vertical_atom_id.append(int(row.pop(0))) 932 vertical_atom_symbol.update( 933 {vertical_atom_id[-1]: str(row.pop(0))}) 934 row_f = [float(item) for item in row] 935 new_rows_full_table.append(row_f) 936 937 if horizontal_atom_id == vertical_atom_id: 938 mayer_bond_order = self.MayerBondOrders(new_rows_full_table, 939 horizontal_atom_id, 940 horizontal_atom_symbol, 941 vertical_atom_symbol) 942 else: 943 raise Exception( 944 "The condition: 'horizontal_atom_id == vertical_atom_id' " 945 + "must be met ") 946 return mayer_bond_order 947 948 @classmethod 949 def _load_unit_cell(cls, file: str): 950 vectors = [] 951 for item in cls._fingerprints_unit_cell: 952 regex = f'(?<={item})[ \S]+' 953 match = re.search(regex, file) 954 if match is None: 955 raise Exception(" wrong file format!!!!") 956 row = match[0].split() 957 row_f = [float(item) for item in row] 958 vectors.append(row_f) 959 unit_cell = UnitCell() 960 unit_cell.lattice_vectors = tuple(item 961 for item in vectors) 962 return unit_cell
x-cartesian coordinate
y-cartesian coordinate
z-cartesian coordinate
euclidean vector
angle in degrees
29class LoadedData(Enum): 30 """Enumeration used by **InputDataFromCPMD**.""" 31 UnitCell = 1 32 MayerBondOrders = 2 33 Populations = 3 34 CoordinatesOfAtoms = 4
Enumeration used by InputDataFromCPMD.
Inherited Members
- enum.Enum
- name
- value
37class Constants: 38 """This class holds constants used in calculations in module 39 **input_data**.""" 40 _CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR: float = 0.52917720859 41 """The length of the bohr radius in angstroms."""
This class holds constants used in calculations in module input_data.
44@dataclass 45class UnitCell(Constants): 46 """Unit cell. 47 48 Object represent unit cell. 49 50 Attributes: 51 a (float): Angstroms 52 b (float): Angstroms 53 c (float): Angstroms 54 lattice_vectors (tuple[vector]): Table represents lattice vectors. 55 alfa (deg): deg 56 beta (deg): deg 57 gamma (deg): deg 58 59 """ 60 61 lattice_vectors: tuple[vector] | tuple = () 62 converted_to_angstroms: bool = False 63 64 a: float = 0 65 b: float = 0 66 c: float = 0 67 68 alfa: deg = 0 69 beta: deg = 0 70 gamma: deg = 0 71 72 def calculate_edges_lengths(self) -> tuple[x, y, z] | None: 73 """Calculate edge lengths 74 75 Example: 76 >>> unit_cell = UnitCell() 77 >>> unit_cell.lattice_vectors = ((1, 0, 0),\ 78 (0, 1, 0),\ 79 (0, 0, 1)) 80 >>> unit_cell.calculate_edges_lengths() 81 (1.0, 1.0, 1.0) 82 >>> unit_cell.a 83 1.0 84 85 Returns: 86 **tuple[x, y, z] | None**: To calculate edges lattice_vectors 87 are required else returns None 88 """ 89 if self.lattice_vectors == (): 90 return None 91 else: 92 self.a = (self.lattice_vectors[0][0] ** 2 93 + self.lattice_vectors[0][1] ** 2 94 + self.lattice_vectors[0][2] ** 2) ** (1/2) 95 self.b = (self.lattice_vectors[1][0] ** 2 96 + self.lattice_vectors[1][1] ** 2 97 + self.lattice_vectors[1][2] ** 2) ** (1/2) 98 self.c = (self.lattice_vectors[2][0] ** 2 99 + self.lattice_vectors[2][1] ** 2 100 + self.lattice_vectors[2][2] ** 2) ** (1/2) 101 return (self.a, self.b, self.c) 102 103 def calculate_interaxial_angles(self) -> tuple[deg, deg, deg] | None: 104 """Calculate interaxial angles. 105 106 Example: 107 >>> unit_cell = UnitCell() 108 >>> unit_cell.lattice_vectors = ((1, 1, 0),\ 109 (0, 1, 0),\ 110 (0, 0, 1)) 111 >>> angles = unit_cell.calculate_interaxial_angles() 112 >>> round(angles[0], 1) 113 90.0 114 >>> round(angles[1], 1) 115 90.0 116 >>> round(angles[2], 1) 117 45.0 118 119 Returns: 120 **tuple[deg, deg, deg] | None**: To calculate angles lattice_vectors 121 are required else returns None 122 """ 123 if self.lattice_vectors == (): 124 return None 125 else: 126 if self.a == 0 or self.b == 0 or self.c == 0: 127 self.calculate_edges_lengths() 128 129 self.alfa = degrees(acos(( 130 self.lattice_vectors[1][0] 131 * self.lattice_vectors[2][0] 132 + self.lattice_vectors[1][1] 133 * self.lattice_vectors[2][1] 134 + self.lattice_vectors[1][2] 135 * self.lattice_vectors[2][2]) 136 / (self.b * self.c))) 137 138 self.beta = degrees(acos(( 139 self.lattice_vectors[0][0] 140 * self.lattice_vectors[2][0] 141 + self.lattice_vectors[0][1] 142 * self.lattice_vectors[2][1] 143 + self.lattice_vectors[0][2] 144 * self.lattice_vectors[2][2]) 145 / (self.a * self.c))) 146 147 self.gamma = degrees(acos(( 148 self.lattice_vectors[0][0] 149 * self.lattice_vectors[1][0] 150 + self.lattice_vectors[0][1] 151 * self.lattice_vectors[1][1] 152 + self.lattice_vectors[0][2] 153 * self.lattice_vectors[1][2]) 154 / (self.a * self.b))) 155 156 return (self.alfa, self.beta, self.gamma) 157 158 def convert_cell_data_to_angstroms(self) -> None: 159 """Convert cell data to angstroms. 160 161 You can use it to convert data in lattice_vectors, a, b and c 162 from Bohr units to angstroms. 163 164 Example: 165 >>> unit_cell = UnitCell() 166 >>> unit_cell.lattice_vectors = ((1, 1, 0),\ 167 (0, 1, 0),\ 168 (0, 0, 1)) 169 >>> unit_cell.convert_cell_data_to_angstroms() 170 >>> unit_cell.lattice_vectors 171 ((0.52917720859, 0.52917720859, 0.0), (0.0, 0.52917720859, 0.0), \ 172(0.0, 0.0, 0.52917720859)) 173 174 """ 175 if self.converted_to_angstroms is False: 176 new_lattice_vectors = [] 177 for vector in self.lattice_vectors: 178 new_lattice_vectors.append(( 179 vector[0] * self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR, 180 vector[1] * self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR, 181 vector[2] * self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR)) 182 183 self.lattice_vectors = tuple(new_lattice_vectors) 184 185 if self.a != 0 and self.b != 0 and self.c != 0: 186 self.a = self.a\ 187 * self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR 188 self.b = self.b\ 189 * self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR 190 self.c = self.c\ 191 * self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR 192 193 self.converted_to_angstroms = True
Unit cell.
Object represent unit cell.
Attributes:
- a (float): Angstroms
- b (float): Angstroms
- c (float): Angstroms
- lattice_vectors (tuple[vector]): Table represents lattice vectors.
- alfa (deg): deg
- beta (deg): deg
- gamma (deg): deg
72 def calculate_edges_lengths(self) -> tuple[x, y, z] | None: 73 """Calculate edge lengths 74 75 Example: 76 >>> unit_cell = UnitCell() 77 >>> unit_cell.lattice_vectors = ((1, 0, 0),\ 78 (0, 1, 0),\ 79 (0, 0, 1)) 80 >>> unit_cell.calculate_edges_lengths() 81 (1.0, 1.0, 1.0) 82 >>> unit_cell.a 83 1.0 84 85 Returns: 86 **tuple[x, y, z] | None**: To calculate edges lattice_vectors 87 are required else returns None 88 """ 89 if self.lattice_vectors == (): 90 return None 91 else: 92 self.a = (self.lattice_vectors[0][0] ** 2 93 + self.lattice_vectors[0][1] ** 2 94 + self.lattice_vectors[0][2] ** 2) ** (1/2) 95 self.b = (self.lattice_vectors[1][0] ** 2 96 + self.lattice_vectors[1][1] ** 2 97 + self.lattice_vectors[1][2] ** 2) ** (1/2) 98 self.c = (self.lattice_vectors[2][0] ** 2 99 + self.lattice_vectors[2][1] ** 2 100 + self.lattice_vectors[2][2] ** 2) ** (1/2) 101 return (self.a, self.b, self.c)
Calculate edge lengths
Example:
>>> unit_cell = UnitCell()
>>> unit_cell.lattice_vectors = ((1, 0, 0), (0, 1, 0), (0, 0, 1))
>>> unit_cell.calculate_edges_lengths()
(1.0, 1.0, 1.0)
>>> unit_cell.a
1.0
Returns:
tuple[x, y, z] | None: To calculate edges lattice_vectors are required else returns None
103 def calculate_interaxial_angles(self) -> tuple[deg, deg, deg] | None: 104 """Calculate interaxial angles. 105 106 Example: 107 >>> unit_cell = UnitCell() 108 >>> unit_cell.lattice_vectors = ((1, 1, 0),\ 109 (0, 1, 0),\ 110 (0, 0, 1)) 111 >>> angles = unit_cell.calculate_interaxial_angles() 112 >>> round(angles[0], 1) 113 90.0 114 >>> round(angles[1], 1) 115 90.0 116 >>> round(angles[2], 1) 117 45.0 118 119 Returns: 120 **tuple[deg, deg, deg] | None**: To calculate angles lattice_vectors 121 are required else returns None 122 """ 123 if self.lattice_vectors == (): 124 return None 125 else: 126 if self.a == 0 or self.b == 0 or self.c == 0: 127 self.calculate_edges_lengths() 128 129 self.alfa = degrees(acos(( 130 self.lattice_vectors[1][0] 131 * self.lattice_vectors[2][0] 132 + self.lattice_vectors[1][1] 133 * self.lattice_vectors[2][1] 134 + self.lattice_vectors[1][2] 135 * self.lattice_vectors[2][2]) 136 / (self.b * self.c))) 137 138 self.beta = degrees(acos(( 139 self.lattice_vectors[0][0] 140 * self.lattice_vectors[2][0] 141 + self.lattice_vectors[0][1] 142 * self.lattice_vectors[2][1] 143 + self.lattice_vectors[0][2] 144 * self.lattice_vectors[2][2]) 145 / (self.a * self.c))) 146 147 self.gamma = degrees(acos(( 148 self.lattice_vectors[0][0] 149 * self.lattice_vectors[1][0] 150 + self.lattice_vectors[0][1] 151 * self.lattice_vectors[1][1] 152 + self.lattice_vectors[0][2] 153 * self.lattice_vectors[1][2]) 154 / (self.a * self.b))) 155 156 return (self.alfa, self.beta, self.gamma)
Calculate interaxial angles.
Example:
>>> unit_cell = UnitCell()
>>> unit_cell.lattice_vectors = ((1, 1, 0), (0, 1, 0), (0, 0, 1))
>>> angles = unit_cell.calculate_interaxial_angles()
>>> round(angles[0], 1)
90.0
>>> round(angles[1], 1)
90.0
>>> round(angles[2], 1)
45.0
Returns:
tuple[deg, deg, deg] | None: To calculate angles lattice_vectors are required else returns None
158 def convert_cell_data_to_angstroms(self) -> None: 159 """Convert cell data to angstroms. 160 161 You can use it to convert data in lattice_vectors, a, b and c 162 from Bohr units to angstroms. 163 164 Example: 165 >>> unit_cell = UnitCell() 166 >>> unit_cell.lattice_vectors = ((1, 1, 0),\ 167 (0, 1, 0),\ 168 (0, 0, 1)) 169 >>> unit_cell.convert_cell_data_to_angstroms() 170 >>> unit_cell.lattice_vectors 171 ((0.52917720859, 0.52917720859, 0.0), (0.0, 0.52917720859, 0.0), \ 172(0.0, 0.0, 0.52917720859)) 173 174 """ 175 if self.converted_to_angstroms is False: 176 new_lattice_vectors = [] 177 for vector in self.lattice_vectors: 178 new_lattice_vectors.append(( 179 vector[0] * self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR, 180 vector[1] * self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR, 181 vector[2] * self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR)) 182 183 self.lattice_vectors = tuple(new_lattice_vectors) 184 185 if self.a != 0 and self.b != 0 and self.c != 0: 186 self.a = self.a\ 187 * self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR 188 self.b = self.b\ 189 * self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR 190 self.c = self.c\ 191 * self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR 192 193 self.converted_to_angstroms = True
Convert cell data to angstroms.
You can use it to convert data in lattice_vectors, a, b and c from Bohr units to angstroms.
Example:
>>> unit_cell = UnitCell()
>>> unit_cell.lattice_vectors = ((1, 1, 0), (0, 1, 0), (0, 0, 1))
>>> unit_cell.convert_cell_data_to_angstroms()
>>> unit_cell.lattice_vectors
((0.52917720859, 0.52917720859, 0.0), (0.0, 0.52917720859, 0.0), (0.0, 0.0, 0.52917720859))
196class Populations: 197 """Populations from MULLIKEN, LOWDIN analysis.""" 198 199 mulliken: dict[int, tuple[str, float]] = {} 200 lodwin: dict[int, tuple[str, float]] = {} 201 valence: dict[int, tuple[str, float]] = {} 202 203 def __init__(self) -> None: 204 pass
Populations from MULLIKEN, LOWDIN analysis.
207class MayerBondOrders: 208 """Object stores Mayer bond orders of atoms pairs.""" 209 rows = list[float] 210 """Type alias.""" 211 mayer_bond_orders: list[rows] = [] 212 horizontal_atom_symbol: dict[int, str] = {} 213 atom_id: list[int] = [] 214 vertical_atom_symbol: dict[int, str] = {} 215 216 def __init__(self, mayer_bond_orders: list[rows], 217 atom_id: list[int], 218 horizontal_atom_symbol: dict[int, str] = {}, 219 vertical_atom_symbol: dict[int, str] = {}): 220 221 if (len(mayer_bond_orders) == len(atom_id) and 222 len(mayer_bond_orders[0]) == len(atom_id)): 223 self.mayer_bond_orders = mayer_bond_orders 224 self.atom_id = atom_id 225 else: 226 raise ValueError("Size of mayer_bond_orders must fit" 227 + "to horizontal_atom_id and " 228 + "vertical_atom_id!!!") 229 230 if (horizontal_atom_symbol != {} and vertical_atom_symbol != {} 231 and len(horizontal_atom_symbol) == len(mayer_bond_orders) 232 and len(vertical_atom_symbol) == len(mayer_bond_orders[0])): 233 self.horizontal_atom_symbol = horizontal_atom_symbol 234 self.vertical_atom_symbol = vertical_atom_symbol 235 elif (horizontal_atom_symbol == {} 236 and vertical_atom_symbol == {}): 237 pass 238 else: 239 raise ValueError("Size of mayer_bond_orders must fit" 240 + "to horizontal_atom_symbol and " 241 + "vertical_atom_symbol!!!") 242 243 def get_mayer_bond_order_between_atoms(self, atom_id_1: int, atom_id_2: 244 int) -> float: 245 """Get mayer bond order between atoms. 246 247 Args: 248 atom_id_1 (int): first atom id (row) 249 atom_id_2 (int): second atom id (column) 250 251 Returns: 252 **float**: bond order 253 """ 254 255 row = self.mayer_bond_orders[self.atom_id.index(atom_id_2)] 256 return row[self.atom_id.index(atom_id_1)] 257 258 def get_atoms_ids(self, atom_symbol: str) -> list[int]: 259 """Get atoms ids in MayerBondOrders. 260 261 Args: 262 atom_symbol (str): Symbol of atom eg. P, Fe, ... 263 264 Returns: 265 **list[int]**: list of ids 266 """ 267 ids = [] 268 for key, value in self.horizontal_atom_symbol.items(): 269 if value == atom_symbol: 270 ids.append(key) 271 272 return ids 273 274 def check_atom_symbol_in_MBO(self, atom_symbol: str) -> bool: 275 """Check atom symbol in MayerBondOrders object. 276 277 Args: 278 atom_symbol (str): Symbol of atom eg. 'P'. 279 280 Returns: 281 **bool**: True if in MayerBondOrders object. 282 283 """ 284 if self.get_atoms_ids(atom_symbol) == []: 285 return False 286 else: 287 return True 288 289 def get_atom_symbols(self, atom_id_1: int, atom_id_2: 290 int) -> tuple[str, str] | None: 291 """Get atom symbols of pair of atoms. 292 293 Args: 294 atom_id_1 (int): atom 1 id 295 atom_id_2 (int): atom 2 id 296 297 Returns: 298 **tuple(str, str) | None**: Symbol of atom 1 and atom 2, 299 if wrong id of atoms returns None or 300 atoms symbols have not been used. 301 302 """ 303 atom_1 = self.horizontal_atom_symbol.get(atom_id_1, None) 304 atom_2 = self.vertical_atom_symbol.get(atom_id_2, None) 305 306 if atom_1 is None or atom_2 is None: 307 return None 308 else: 309 return (atom_1, atom_2) 310 311 def get_all_mayer_bond_orders_of_atom(self, atom_id) -> list[float]: 312 """Returns list of mayer bond orders of atom of given id. 313 314 Args: 315 atom_id (int): atom id. 316 317 Returns: 318 **list[float]**: list of mayer bond orders. 319 """ 320 row = self.mayer_bond_orders[self.atom_id.index(atom_id)] 321 return row 322 323 def get_mayer_bond_orders_list_between_two_atoms(self, atom_symbol_1: str, 324 atom_symbol_2: str 325 ) -> list[float]: 326 """Get Mayer bond orders list between two atoms. 327 328 Args: 329 atom_symbol_1 (str): atom symbol eg. "Fe" 330 atom_symbol_2 (str): atom symbol eg. "Fe" 331 332 Returns: 333 **list[float]**: list of mayer bond orders 334 """ 335 mayer_bond_orders = [] 336 for atom_id_1 in self.atom_id: 337 if atom_symbol_1 == self.vertical_atom_symbol.get(atom_id_1): 338 for atom_id_2 in range(atom_id_1 + 1, len(self.atom_id) + 1): 339 if atom_symbol_2 == self.vertical_atom_symbol.get(atom_id_2): 340 mayer_bond_orders.append( 341 self.get_mayer_bond_order_between_atoms(atom_id_1, 342 atom_id_2)) 343 344 return mayer_bond_orders
Object stores Mayer bond orders of atoms pairs.
216 def __init__(self, mayer_bond_orders: list[rows], 217 atom_id: list[int], 218 horizontal_atom_symbol: dict[int, str] = {}, 219 vertical_atom_symbol: dict[int, str] = {}): 220 221 if (len(mayer_bond_orders) == len(atom_id) and 222 len(mayer_bond_orders[0]) == len(atom_id)): 223 self.mayer_bond_orders = mayer_bond_orders 224 self.atom_id = atom_id 225 else: 226 raise ValueError("Size of mayer_bond_orders must fit" 227 + "to horizontal_atom_id and " 228 + "vertical_atom_id!!!") 229 230 if (horizontal_atom_symbol != {} and vertical_atom_symbol != {} 231 and len(horizontal_atom_symbol) == len(mayer_bond_orders) 232 and len(vertical_atom_symbol) == len(mayer_bond_orders[0])): 233 self.horizontal_atom_symbol = horizontal_atom_symbol 234 self.vertical_atom_symbol = vertical_atom_symbol 235 elif (horizontal_atom_symbol == {} 236 and vertical_atom_symbol == {}): 237 pass 238 else: 239 raise ValueError("Size of mayer_bond_orders must fit" 240 + "to horizontal_atom_symbol and " 241 + "vertical_atom_symbol!!!")
243 def get_mayer_bond_order_between_atoms(self, atom_id_1: int, atom_id_2: 244 int) -> float: 245 """Get mayer bond order between atoms. 246 247 Args: 248 atom_id_1 (int): first atom id (row) 249 atom_id_2 (int): second atom id (column) 250 251 Returns: 252 **float**: bond order 253 """ 254 255 row = self.mayer_bond_orders[self.atom_id.index(atom_id_2)] 256 return row[self.atom_id.index(atom_id_1)]
Get mayer bond order between atoms.
Arguments:
- atom_id_1 (int): first atom id (row)
- atom_id_2 (int): second atom id (column)
Returns:
float: bond order
258 def get_atoms_ids(self, atom_symbol: str) -> list[int]: 259 """Get atoms ids in MayerBondOrders. 260 261 Args: 262 atom_symbol (str): Symbol of atom eg. P, Fe, ... 263 264 Returns: 265 **list[int]**: list of ids 266 """ 267 ids = [] 268 for key, value in self.horizontal_atom_symbol.items(): 269 if value == atom_symbol: 270 ids.append(key) 271 272 return ids
Get atoms ids in MayerBondOrders.
Arguments:
- atom_symbol (str): Symbol of atom eg. P, Fe, ...
Returns:
list[int]: list of ids
274 def check_atom_symbol_in_MBO(self, atom_symbol: str) -> bool: 275 """Check atom symbol in MayerBondOrders object. 276 277 Args: 278 atom_symbol (str): Symbol of atom eg. 'P'. 279 280 Returns: 281 **bool**: True if in MayerBondOrders object. 282 283 """ 284 if self.get_atoms_ids(atom_symbol) == []: 285 return False 286 else: 287 return True
Check atom symbol in MayerBondOrders object.
Arguments:
- atom_symbol (str): Symbol of atom eg. 'P'.
Returns:
bool: True if in MayerBondOrders object.
289 def get_atom_symbols(self, atom_id_1: int, atom_id_2: 290 int) -> tuple[str, str] | None: 291 """Get atom symbols of pair of atoms. 292 293 Args: 294 atom_id_1 (int): atom 1 id 295 atom_id_2 (int): atom 2 id 296 297 Returns: 298 **tuple(str, str) | None**: Symbol of atom 1 and atom 2, 299 if wrong id of atoms returns None or 300 atoms symbols have not been used. 301 302 """ 303 atom_1 = self.horizontal_atom_symbol.get(atom_id_1, None) 304 atom_2 = self.vertical_atom_symbol.get(atom_id_2, None) 305 306 if atom_1 is None or atom_2 is None: 307 return None 308 else: 309 return (atom_1, atom_2)
Get atom symbols of pair of atoms.
Arguments:
- atom_id_1 (int): atom 1 id
- atom_id_2 (int): atom 2 id
Returns:
tuple(str, str) | None: Symbol of atom 1 and atom 2, if wrong id of atoms returns None or atoms symbols have not been used.
311 def get_all_mayer_bond_orders_of_atom(self, atom_id) -> list[float]: 312 """Returns list of mayer bond orders of atom of given id. 313 314 Args: 315 atom_id (int): atom id. 316 317 Returns: 318 **list[float]**: list of mayer bond orders. 319 """ 320 row = self.mayer_bond_orders[self.atom_id.index(atom_id)] 321 return row
Returns list of mayer bond orders of atom of given id.
Arguments:
- atom_id (int): atom id.
Returns:
list[float]: list of mayer bond orders.
323 def get_mayer_bond_orders_list_between_two_atoms(self, atom_symbol_1: str, 324 atom_symbol_2: str 325 ) -> list[float]: 326 """Get Mayer bond orders list between two atoms. 327 328 Args: 329 atom_symbol_1 (str): atom symbol eg. "Fe" 330 atom_symbol_2 (str): atom symbol eg. "Fe" 331 332 Returns: 333 **list[float]**: list of mayer bond orders 334 """ 335 mayer_bond_orders = [] 336 for atom_id_1 in self.atom_id: 337 if atom_symbol_1 == self.vertical_atom_symbol.get(atom_id_1): 338 for atom_id_2 in range(atom_id_1 + 1, len(self.atom_id) + 1): 339 if atom_symbol_2 == self.vertical_atom_symbol.get(atom_id_2): 340 mayer_bond_orders.append( 341 self.get_mayer_bond_order_between_atoms(atom_id_1, 342 atom_id_2)) 343 344 return mayer_bond_orders
Get Mayer bond orders list between two atoms.
Arguments:
- atom_symbol_1 (str): atom symbol eg. "Fe"
- atom_symbol_2 (str): atom symbol eg. "Fe"
Returns:
list[float]: list of mayer bond orders
347class CoordinatesOfAtoms(Constants): 348 """Object represents coordinates of atoms. 349 350 Args: 351 atom_coordinates_table (list[tuple[atom_id, str, x, y, z]], optional): \ 352 Table with atom coordinates. 353 354 Attributes: 355 ids (list[atom_id]): ids of atoms. 356 atom_symbols (dict[atom_id, str]): \ 357 Dictionary storing symbols of atoms, key is atom id. 358 359 Types: 360 atom_id = int 361 362 """ 363 364 atom_id: TypeAlias = int 365 """Type alias""" 366 ids: list[atom_id] 367 _coordinates: dict[atom_id, vector] 368 atom_symbols: dict[atom_id, str] 369 _unit_cell: UnitCell | None 370 371 def __init__(self, atom_coordinates_table: list[tuple[atom_id, str, x, y, 372 z]] = []) -> None: 373 374 self.ids = [] 375 self._coordinates = {} 376 self.atom_symbols = {} 377 self._unit_cell = None 378 379 if atom_coordinates_table != []: 380 while atom_coordinates_table != []: 381 row = atom_coordinates_table.pop(0) 382 self.ids.append(row[0]) 383 self.atom_symbols.update({row[0]: row[1]}) 384 self._coordinates.update({row[0]: (row[2], row[3], row[4])}) 385 386 def add_new_atom(self, id: int, atom_symbol: str, coordinates: 387 tuple[x, y, z]) -> None: 388 """Add new atom coordinates to CoordinatesOfAtoms object. 389 390 Args: 391 id (int): id of new atom. 392 atom_symbol (str): Symbol of new atom. 393 coordinates (tuple[x, y, z]): Coordinates of new atom. 394 395 """ 396 self.ids.append(id) 397 self.atom_symbols.update({id: atom_symbol}) 398 self._coordinates.update({id: coordinates}) 399 400 def get_atom_coordinates(self, id: int) -> tuple[x, y, z] | None: 401 """Get atoms coordinates. 402 403 Args: 404 id (int): atom id 405 406 Returns: 407 **tuple[x, y, z] | None**: Returns atom coordinates or None if atom of 408 given id does not exist. 409 410 """ 411 return self._coordinates.get(id, None) 412 413 def get_atom_coordinates_converted_in_angstrom(self, id: int)\ 414 -> tuple[x, y, z] | None: 415 """Get atom coordinates converted in angstroms if they were stored as 416 Bohr units. Not change stored values. 417 418 Example: 419 >>> coordinates_of_atoms = CoordinatesOfAtoms() 420 >>> coordinates_of_atoms.add_new_atom(1, 'P', (1, 1, 1)) 421 >>> coordinates_of_atoms.get_atom_coordinates_converted_in_angstrom(1) 422 (0.52917720859, 0.52917720859, 0.52917720859) 423 424 Args: 425 id (int): atom id 426 427 Returns: 428 **tuple[x, y, z]**: coordinates in angstroms 429 430 """ 431 temp_coordinates = self._coordinates.get(id, None) 432 if temp_coordinates is not None: 433 coordinates = ( 434 self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR 435 * temp_coordinates[0], 436 self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR 437 * temp_coordinates[1], 438 self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR 439 * temp_coordinates[2]) 440 return coordinates 441 else: 442 return None 443 444 def convert_stored_coordinates_to_angstroms(self) -> None: 445 """Converts stored coordinates from bohr units to angstroms. 446 447 Example: 448 >>> coordinates_of_atoms = CoordinatesOfAtoms() 449 >>> coordinates_of_atoms.add_new_atom(1, 'P', (1, 1, 1)) 450 >>> coordinates_of_atoms.add_new_atom(2, 'P', (0, 0, 0)) 451 >>> coordinates_of_atoms.convert_stored_coordinates_to_angstroms() 452 >>> coordinates_of_atoms.get_atom_coordinates(1) 453 (0.52917720859, 0.52917720859, 0.52917720859) 454 455 """ 456 457 for id, coord in self._coordinates.items(): 458 self._coordinates[id] = ( 459 self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR * coord[0], 460 self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR * coord[1], 461 self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR * coord[2], 462 ) 463 464 def get_atom_symbol(self, id: int) -> str | None: 465 """Get atom symbol 466 467 Args: 468 id (int): atom id 469 470 Returns: 471 **Union[str, None]**: Returns atom symbol or None if atom of 472 given id does not exist. 473 474 """ 475 return self.atom_symbols.get(id) 476 477 @property 478 def coordinates(self) -> list[vector]: 479 coordinates = [value for value in self._coordinates.values()] 480 return coordinates 481 482 def add_unit_cell(self, unit_cell: UnitCell): 483 self._unit_cell = unit_cell 484 485 def remove_unit_cell(self): 486 self._unit_cell = None 487 488 def get_distance_between_atoms(self, atom_id_1: int, atom_id_2: int)\ 489 -> float | None: 490 """ Calculate distance between two atoms. 491 492 Args: 493 atom_id_1 (int): id of first atom 494 atom_id_2 (int): id of second atom 495 Returns: 496 **float | None**: Distance in angstroms or Bohr units. 497 498 """ 499 500 if type(self._unit_cell) is not UnitCell: 501 raise Exception("Unit cell not added or wrong type of" 502 + " unit_cell, use .add_unit_cell(self, unit_cell) method!!!") 503 504 atom_coord_1_v = self._coordinates.get(atom_id_1, None) 505 atom_coord_2_v = self._coordinates.get(atom_id_2, None) 506 507 if atom_coord_1_v is None or atom_coord_2_v is None: 508 return None 509 else: 510 atom_coord_1 = np.array(atom_coord_1_v) 511 atom_coord_2 = np.array(atom_coord_2_v) 512 513 vector_between_atoms = atom_coord_1 - atom_coord_2 514 515 length = float(np.linalg.norm(vector_between_atoms)) 516 517 lattice_vectors_list = [] 518 for vector in self._unit_cell.lattice_vectors: 519 lattice_vectors_list.append(np.array(vector)) 520 521 def get_lower_distance(length: float, vector_1: NDArray, 522 vector_2: NDArray) -> float: 523 vector_between_atoms = vector_1 - vector_2 524 525 new_length = float(np.linalg.norm(vector_between_atoms)) 526 527 if new_length < length: 528 length = new_length 529 530 return length 531 532 for vector in lattice_vectors_list: 533 atom_2_coord_in_neighboring_unit = atom_coord_2 + vector 534 535 length = get_lower_distance(length, atom_coord_1, 536 atom_2_coord_in_neighboring_unit) 537 538 for vector in lattice_vectors_list: 539 atom_2_coord_in_neighboring_unit = atom_coord_2 - vector 540 541 length = get_lower_distance(length, atom_coord_1, 542 atom_2_coord_in_neighboring_unit) 543 544 for i in range(3): 545 for j in range(i + 1, 3): 546 translation = lattice_vectors_list[i] + lattice_vectors_list[j] 547 atom_2_coord_in_neighboring_unit = atom_coord_2 + translation 548 549 length = get_lower_distance(length, atom_coord_1, 550 atom_2_coord_in_neighboring_unit) 551 552 atom_2_coord_in_neighboring_unit = atom_coord_2 - translation 553 554 length = get_lower_distance(length, atom_coord_1, 555 atom_2_coord_in_neighboring_unit) 556 557 translation = lattice_vectors_list[i] - lattice_vectors_list[j] 558 atom_2_coord_in_neighboring_unit = atom_coord_2 + translation 559 560 length = get_lower_distance(length, atom_coord_1, 561 atom_2_coord_in_neighboring_unit) 562 563 atom_2_coord_in_neighboring_unit = atom_coord_2 - translation 564 565 length = get_lower_distance(length, atom_coord_1, 566 atom_2_coord_in_neighboring_unit) 567 568 translation = lattice_vectors_list[0] + lattice_vectors_list[1]\ 569 + lattice_vectors_list[2] 570 571 atom_2_coord_in_neighboring_unit = atom_coord_2 + translation 572 573 length = get_lower_distance(length, atom_coord_1, 574 atom_2_coord_in_neighboring_unit) 575 576 atom_2_coord_in_neighboring_unit = atom_coord_2 - translation 577 578 length = get_lower_distance(length, atom_coord_1, 579 atom_2_coord_in_neighboring_unit) 580 581 translation = lattice_vectors_list[0] - lattice_vectors_list[1]\ 582 + lattice_vectors_list[2] 583 584 atom_2_coord_in_neighboring_unit = atom_coord_2 + translation 585 586 length = get_lower_distance(length, atom_coord_1, 587 atom_2_coord_in_neighboring_unit) 588 589 atom_2_coord_in_neighboring_unit = atom_coord_2 - translation 590 591 length = get_lower_distance(length, atom_coord_1, 592 atom_2_coord_in_neighboring_unit) 593 594 translation = lattice_vectors_list[0] - lattice_vectors_list[1]\ 595 - lattice_vectors_list[2] 596 597 atom_2_coord_in_neighboring_unit = atom_coord_2 + translation 598 599 length = get_lower_distance(length, atom_coord_1, 600 atom_2_coord_in_neighboring_unit) 601 602 atom_2_coord_in_neighboring_unit = atom_coord_2 - translation 603 604 length = get_lower_distance(length, atom_coord_1, 605 atom_2_coord_in_neighboring_unit) 606 607 translation = lattice_vectors_list[0] + lattice_vectors_list[1]\ 608 - lattice_vectors_list[2] 609 610 atom_2_coord_in_neighboring_unit = atom_coord_2 + translation 611 612 length = get_lower_distance(length, atom_coord_1, 613 atom_2_coord_in_neighboring_unit) 614 615 atom_2_coord_in_neighboring_unit = atom_coord_2 - translation 616 617 length = get_lower_distance(length, atom_coord_1, 618 atom_2_coord_in_neighboring_unit) 619 620 return length
Object represents coordinates of atoms.
Arguments:
- atom_coordinates_table (list[tuple[atom_id, str, x, y, z]], optional): Table with atom coordinates.
Attributes:
- ids (list[atom_id]): ids of atoms.
- atom_symbols (dict[atom_id, str]): Dictionary storing symbols of atoms, key is atom id.
Types:
atom_id = int
371 def __init__(self, atom_coordinates_table: list[tuple[atom_id, str, x, y, 372 z]] = []) -> None: 373 374 self.ids = [] 375 self._coordinates = {} 376 self.atom_symbols = {} 377 self._unit_cell = None 378 379 if atom_coordinates_table != []: 380 while atom_coordinates_table != []: 381 row = atom_coordinates_table.pop(0) 382 self.ids.append(row[0]) 383 self.atom_symbols.update({row[0]: row[1]}) 384 self._coordinates.update({row[0]: (row[2], row[3], row[4])})
386 def add_new_atom(self, id: int, atom_symbol: str, coordinates: 387 tuple[x, y, z]) -> None: 388 """Add new atom coordinates to CoordinatesOfAtoms object. 389 390 Args: 391 id (int): id of new atom. 392 atom_symbol (str): Symbol of new atom. 393 coordinates (tuple[x, y, z]): Coordinates of new atom. 394 395 """ 396 self.ids.append(id) 397 self.atom_symbols.update({id: atom_symbol}) 398 self._coordinates.update({id: coordinates})
Add new atom coordinates to CoordinatesOfAtoms object.
Arguments:
- id (int): id of new atom.
- atom_symbol (str): Symbol of new atom.
- coordinates (tuple[x, y, z]): Coordinates of new atom.
400 def get_atom_coordinates(self, id: int) -> tuple[x, y, z] | None: 401 """Get atoms coordinates. 402 403 Args: 404 id (int): atom id 405 406 Returns: 407 **tuple[x, y, z] | None**: Returns atom coordinates or None if atom of 408 given id does not exist. 409 410 """ 411 return self._coordinates.get(id, None)
Get atoms coordinates.
Arguments:
- id (int): atom id
Returns:
tuple[x, y, z] | None: Returns atom coordinates or None if atom of given id does not exist.
413 def get_atom_coordinates_converted_in_angstrom(self, id: int)\ 414 -> tuple[x, y, z] | None: 415 """Get atom coordinates converted in angstroms if they were stored as 416 Bohr units. Not change stored values. 417 418 Example: 419 >>> coordinates_of_atoms = CoordinatesOfAtoms() 420 >>> coordinates_of_atoms.add_new_atom(1, 'P', (1, 1, 1)) 421 >>> coordinates_of_atoms.get_atom_coordinates_converted_in_angstrom(1) 422 (0.52917720859, 0.52917720859, 0.52917720859) 423 424 Args: 425 id (int): atom id 426 427 Returns: 428 **tuple[x, y, z]**: coordinates in angstroms 429 430 """ 431 temp_coordinates = self._coordinates.get(id, None) 432 if temp_coordinates is not None: 433 coordinates = ( 434 self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR 435 * temp_coordinates[0], 436 self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR 437 * temp_coordinates[1], 438 self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR 439 * temp_coordinates[2]) 440 return coordinates 441 else: 442 return None
Get atom coordinates converted in angstroms if they were stored as Bohr units. Not change stored values.
Example:
>>> coordinates_of_atoms = CoordinatesOfAtoms()
>>> coordinates_of_atoms.add_new_atom(1, 'P', (1, 1, 1))
>>> coordinates_of_atoms.get_atom_coordinates_converted_in_angstrom(1)
(0.52917720859, 0.52917720859, 0.52917720859)
Arguments:
- id (int): atom id
Returns:
tuple[x, y, z]: coordinates in angstroms
444 def convert_stored_coordinates_to_angstroms(self) -> None: 445 """Converts stored coordinates from bohr units to angstroms. 446 447 Example: 448 >>> coordinates_of_atoms = CoordinatesOfAtoms() 449 >>> coordinates_of_atoms.add_new_atom(1, 'P', (1, 1, 1)) 450 >>> coordinates_of_atoms.add_new_atom(2, 'P', (0, 0, 0)) 451 >>> coordinates_of_atoms.convert_stored_coordinates_to_angstroms() 452 >>> coordinates_of_atoms.get_atom_coordinates(1) 453 (0.52917720859, 0.52917720859, 0.52917720859) 454 455 """ 456 457 for id, coord in self._coordinates.items(): 458 self._coordinates[id] = ( 459 self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR * coord[0], 460 self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR * coord[1], 461 self._CONSTANT_TO_CALCULATE_ANGSTROMS_FROM_BOHR * coord[2], 462 )
Converts stored coordinates from bohr units to angstroms.
Example:
>>> coordinates_of_atoms = CoordinatesOfAtoms()
>>> coordinates_of_atoms.add_new_atom(1, 'P', (1, 1, 1))
>>> coordinates_of_atoms.add_new_atom(2, 'P', (0, 0, 0))
>>> coordinates_of_atoms.convert_stored_coordinates_to_angstroms()
>>> coordinates_of_atoms.get_atom_coordinates(1)
(0.52917720859, 0.52917720859, 0.52917720859)
464 def get_atom_symbol(self, id: int) -> str | None: 465 """Get atom symbol 466 467 Args: 468 id (int): atom id 469 470 Returns: 471 **Union[str, None]**: Returns atom symbol or None if atom of 472 given id does not exist. 473 474 """ 475 return self.atom_symbols.get(id)
Get atom symbol
Arguments:
- id (int): atom id
Returns:
Union[str, None]: Returns atom symbol or None if atom of given id does not exist.
488 def get_distance_between_atoms(self, atom_id_1: int, atom_id_2: int)\ 489 -> float | None: 490 """ Calculate distance between two atoms. 491 492 Args: 493 atom_id_1 (int): id of first atom 494 atom_id_2 (int): id of second atom 495 Returns: 496 **float | None**: Distance in angstroms or Bohr units. 497 498 """ 499 500 if type(self._unit_cell) is not UnitCell: 501 raise Exception("Unit cell not added or wrong type of" 502 + " unit_cell, use .add_unit_cell(self, unit_cell) method!!!") 503 504 atom_coord_1_v = self._coordinates.get(atom_id_1, None) 505 atom_coord_2_v = self._coordinates.get(atom_id_2, None) 506 507 if atom_coord_1_v is None or atom_coord_2_v is None: 508 return None 509 else: 510 atom_coord_1 = np.array(atom_coord_1_v) 511 atom_coord_2 = np.array(atom_coord_2_v) 512 513 vector_between_atoms = atom_coord_1 - atom_coord_2 514 515 length = float(np.linalg.norm(vector_between_atoms)) 516 517 lattice_vectors_list = [] 518 for vector in self._unit_cell.lattice_vectors: 519 lattice_vectors_list.append(np.array(vector)) 520 521 def get_lower_distance(length: float, vector_1: NDArray, 522 vector_2: NDArray) -> float: 523 vector_between_atoms = vector_1 - vector_2 524 525 new_length = float(np.linalg.norm(vector_between_atoms)) 526 527 if new_length < length: 528 length = new_length 529 530 return length 531 532 for vector in lattice_vectors_list: 533 atom_2_coord_in_neighboring_unit = atom_coord_2 + vector 534 535 length = get_lower_distance(length, atom_coord_1, 536 atom_2_coord_in_neighboring_unit) 537 538 for vector in lattice_vectors_list: 539 atom_2_coord_in_neighboring_unit = atom_coord_2 - vector 540 541 length = get_lower_distance(length, atom_coord_1, 542 atom_2_coord_in_neighboring_unit) 543 544 for i in range(3): 545 for j in range(i + 1, 3): 546 translation = lattice_vectors_list[i] + lattice_vectors_list[j] 547 atom_2_coord_in_neighboring_unit = atom_coord_2 + translation 548 549 length = get_lower_distance(length, atom_coord_1, 550 atom_2_coord_in_neighboring_unit) 551 552 atom_2_coord_in_neighboring_unit = atom_coord_2 - translation 553 554 length = get_lower_distance(length, atom_coord_1, 555 atom_2_coord_in_neighboring_unit) 556 557 translation = lattice_vectors_list[i] - lattice_vectors_list[j] 558 atom_2_coord_in_neighboring_unit = atom_coord_2 + translation 559 560 length = get_lower_distance(length, atom_coord_1, 561 atom_2_coord_in_neighboring_unit) 562 563 atom_2_coord_in_neighboring_unit = atom_coord_2 - translation 564 565 length = get_lower_distance(length, atom_coord_1, 566 atom_2_coord_in_neighboring_unit) 567 568 translation = lattice_vectors_list[0] + lattice_vectors_list[1]\ 569 + lattice_vectors_list[2] 570 571 atom_2_coord_in_neighboring_unit = atom_coord_2 + translation 572 573 length = get_lower_distance(length, atom_coord_1, 574 atom_2_coord_in_neighboring_unit) 575 576 atom_2_coord_in_neighboring_unit = atom_coord_2 - translation 577 578 length = get_lower_distance(length, atom_coord_1, 579 atom_2_coord_in_neighboring_unit) 580 581 translation = lattice_vectors_list[0] - lattice_vectors_list[1]\ 582 + lattice_vectors_list[2] 583 584 atom_2_coord_in_neighboring_unit = atom_coord_2 + translation 585 586 length = get_lower_distance(length, atom_coord_1, 587 atom_2_coord_in_neighboring_unit) 588 589 atom_2_coord_in_neighboring_unit = atom_coord_2 - translation 590 591 length = get_lower_distance(length, atom_coord_1, 592 atom_2_coord_in_neighboring_unit) 593 594 translation = lattice_vectors_list[0] - lattice_vectors_list[1]\ 595 - lattice_vectors_list[2] 596 597 atom_2_coord_in_neighboring_unit = atom_coord_2 + translation 598 599 length = get_lower_distance(length, atom_coord_1, 600 atom_2_coord_in_neighboring_unit) 601 602 atom_2_coord_in_neighboring_unit = atom_coord_2 - translation 603 604 length = get_lower_distance(length, atom_coord_1, 605 atom_2_coord_in_neighboring_unit) 606 607 translation = lattice_vectors_list[0] + lattice_vectors_list[1]\ 608 - lattice_vectors_list[2] 609 610 atom_2_coord_in_neighboring_unit = atom_coord_2 + translation 611 612 length = get_lower_distance(length, atom_coord_1, 613 atom_2_coord_in_neighboring_unit) 614 615 atom_2_coord_in_neighboring_unit = atom_coord_2 - translation 616 617 length = get_lower_distance(length, atom_coord_1, 618 atom_2_coord_in_neighboring_unit) 619 620 return length
Calculate distance between two atoms.
Arguments:
- atom_id_1 (int): id of first atom atom_id_2 (int): id of second atom
Returns:
float | None: Distance in angstroms or Bohr units.
623class InputData(ABC): 624 """Input data. 625 626 Load data from file and create objects. 627 628 """ 629 630 populations: Populations | None = None 631 unit_cell: UnitCell | None = None 632 mayer_bond_orders: MayerBondOrders | None = None 633 coordinates_of_atoms: CoordinatesOfAtoms | None = None 634 635 def __init__(self, Populations: type = Populations, 636 UnitCell: type = UnitCell, 637 MayerBondOrders: type = MayerBondOrders, 638 CoordinatesOfAtoms: type = CoordinatesOfAtoms): 639 """Constructor. 640 641 Args: 642 Populations (type, optional): Defaults to Populations. 643 UnitCell (type, optional): Defaults to UnitCell. 644 MayerBondOrders (type, optional): Defaults to MayerBondOrders. 645 CoordinatesOfAtoms (type, optional): Defaults to CoordinatesOfAtoms. 646 647 """ 648 649 self.Populations = Populations 650 self.UnitCell = UnitCell 651 self.MayerBondOrders = MayerBondOrders 652 self.CoordinatesOfAtoms = CoordinatesOfAtoms 653 self.LoadedData = LoadedData 654 655 @abstractmethod 656 def load_input_data(self, path: str, *args) -> None: 657 """Load input data from file. 658 659 Args: 660 path (str): Path to file. 661 *args: LoadData.UnitCell, LoadedData.MayerBondOrders,\ 662 LoadData.Populations or LoadData.CoordinatesOfAtoms 663 664 """ 665 pass 666 667 @staticmethod 668 def check_is_correct_file(data_in_file: str, fingerprint: str) -> bool: 669 if fingerprint in data_in_file: 670 return True 671 else: 672 return False
Input data.
Load data from file and create objects.
635 def __init__(self, Populations: type = Populations, 636 UnitCell: type = UnitCell, 637 MayerBondOrders: type = MayerBondOrders, 638 CoordinatesOfAtoms: type = CoordinatesOfAtoms): 639 """Constructor. 640 641 Args: 642 Populations (type, optional): Defaults to Populations. 643 UnitCell (type, optional): Defaults to UnitCell. 644 MayerBondOrders (type, optional): Defaults to MayerBondOrders. 645 CoordinatesOfAtoms (type, optional): Defaults to CoordinatesOfAtoms. 646 647 """ 648 649 self.Populations = Populations 650 self.UnitCell = UnitCell 651 self.MayerBondOrders = MayerBondOrders 652 self.CoordinatesOfAtoms = CoordinatesOfAtoms 653 self.LoadedData = LoadedData
Constructor.
Arguments:
- Populations (type, optional): Defaults to Populations.
- UnitCell (type, optional): Defaults to UnitCell.
- MayerBondOrders (type, optional): Defaults to MayerBondOrders.
- CoordinatesOfAtoms (type, optional): Defaults to CoordinatesOfAtoms.
655 @abstractmethod 656 def load_input_data(self, path: str, *args) -> None: 657 """Load input data from file. 658 659 Args: 660 path (str): Path to file. 661 *args: LoadData.UnitCell, LoadedData.MayerBondOrders,\ 662 LoadData.Populations or LoadData.CoordinatesOfAtoms 663 664 """ 665 pass
Load input data from file.
Arguments:
- path (str): Path to file.
- *args: LoadData.UnitCell, LoadedData.MayerBondOrders, LoadData.Populations or LoadData.CoordinatesOfAtoms
675class InputDataFromCPMD(InputData): 676 """Loads data from CPMD file and create objects.""" 677 _fingerprint: str = "The CPMD consortium" 678 _fingerprint_beginning_populations: str = "POPULATION ANALYSIS FROM PROJECTED"\ 679 + " WAVEFUNCTIONS" 680 _fingerprint_end_populations: str = "ChkSum\(POP_MUL\)" 681 _fingerprint_coordinates_of_atoms: str = "ATOM COORDINATES"\ 682 + " CHARGES" 683 _fingerprint_end_coordinates_of_atoms: str = "ChkSum\(CHARGES\)" 684 _fingerprint_mayer_bond_orders: str = "MAYER BOND ORDERS FROM PROJECTED"\ 685 + " WAVEFUNCTIONS" 686 687 _fingerprints_unit_cell: tuple[str, str, str] = ("LATTICE VECTOR A1\(BOHR\):", 688 "LATTICE VECTOR A2\(BOHR\):", 689 "LATTICE VECTOR A3\(BOHR\):") 690 691 _valid_population_columns_names: tuple[str, str, str, str] = ( 692 'ATOM', 'MULLIKEN', 'LOWDIN', 'VALENCE') 693 694 _valid_coordinatios_columns_names: tuple[str, str, str] = ( 695 'X', 'Y', 'Z') 696 697 def load_input_data(self, path: str, *args) -> None: 698 """Loads input data from CPMD file. 699 700 Args: 701 path (str): path to file 702 *args: LoadData.UnitCell, LoadedData.MayerBondOrders, 703 LoadData.Populations or LoadData.CoordinatesOfAtoms 704 705 """ 706 with open(path, 'r') as file: 707 data = file.read() 708 709 if self.check_is_correct_file(data, self._fingerprint) is not True: 710 raise Exception("Wrong input file!") 711 else: 712 if self.LoadedData.UnitCell in args: 713 self.unit_cell = self._load_unit_cell(data) 714 if self.LoadedData.MayerBondOrders in args: 715 self.mayer_bond_orders = \ 716 self._load_mayer_bond_orders(data) 717 if self.LoadedData.Populations in args: 718 self.populations = self._load_populations(data) 719 if self.LoadedData.CoordinatesOfAtoms in args: 720 self.coordinates_of_atoms = \ 721 self._load_coordinates_of_atoms(data) 722 723 def return_data(self, loaded_data: LoadedData)\ 724 -> Populations | UnitCell | MayerBondOrders | CoordinatesOfAtoms\ 725 | None: 726 """ Returns loaded data. 727 728 Args: 729 730 Returns: 731 **Populations | UnitCell | MayerBondOrders | CoordinatesOfAtoms**: 732 733 """ 734 if loaded_data is LoadedData.UnitCell: 735 return self.unit_cell 736 elif loaded_data is LoadedData.MayerBondOrders: 737 return self.mayer_bond_orders 738 elif loaded_data is LoadedData.Populations: 739 return self.populations 740 elif loaded_data is LoadedData.CoordinatesOfAtoms: 741 return self.coordinates_of_atoms 742 else: 743 raise TypeError("Type is not LoadedData!!!!") 744 745 def _load_populations(self, file: str) -> Populations | None: 746 747 rows = self._get_rows_of_data_from_file(file, 748 self._fingerprint_beginning_populations, 749 self._fingerprint_end_populations) 750 labels: list = [] 751 if rows is not None: 752 for row in rows: 753 have_string = True if re.search('[a-zA-Z]+', row) is \ 754 not None else False 755 756 have_number = True if re.search('[0-9]+', row) is \ 757 not None else False 758 759 if have_string and not have_number: 760 labels = row.split() 761 populations = Populations() 762 continue 763 elif have_string and have_number: 764 splited = row.split() 765 if len(labels) + 1 != len(splited): 766 raise Exception( 767 'Wrong quantity of columns in input file!') 768 else: 769 populations = self._add_populations_attributes( 770 populations, splited, labels) 771 return populations 772 else: 773 return None 774 775 @classmethod 776 def _add_populations_attributes(cls, populations: Populations, 777 splited: list, labels: list[str])\ 778 -> Populations: 779 i = 0 780 for item in splited: 781 if i == 0: 782 id = int(item) 783 elif labels[i-1] == cls._valid_population_columns_names[0]: 784 symbol = item 785 elif labels[i-1] == cls._valid_population_columns_names[1]: 786 mulliken_value = float(item) 787 elif labels[i-1] == cls._valid_population_columns_names[2]: 788 lowdin_value = float(item) 789 elif labels[i-1] == cls._valid_population_columns_names[3]: 790 valence_value = float(item) 791 i += 1 792 793 populations.lodwin.update({id: (symbol, lowdin_value)}) 794 populations.mulliken.update({id: (symbol, mulliken_value)}) 795 populations.valence.update({id: (symbol, valence_value)}) 796 797 return populations 798 799 def _load_coordinates_of_atoms(self, file: str)\ 800 -> CoordinatesOfAtoms | None: 801 rows = self._get_rows_of_data_from_file(file, 802 self._fingerprint_coordinates_of_atoms, 803 self._fingerprint_end_coordinates_of_atoms) 804 if rows is not None: 805 for row in rows: 806 have_string = True if re.search('[a-zA-Z]+', row) is \ 807 not None else False 808 809 have_number = True if re.search('[0-9]+', row) is \ 810 not None else False 811 812 if have_string and not have_number: 813 labels = row.split() 814 coordinates_of_atoms = self.CoordinatesOfAtoms() 815 continue 816 elif have_string and have_number: 817 splited = row.split() 818 if len(labels) + 2 != len(splited): 819 raise Exception( 820 'Wrong quantity of columns in input file!') 821 else: 822 coordinates_of_atoms = self._add_coordinations_attributes( 823 coordinates_of_atoms, splited, labels 824 ) 825 return coordinates_of_atoms 826 else: 827 return None 828 829 @classmethod 830 def _add_coordinations_attributes(cls, coordinates_of_atoms: 831 CoordinatesOfAtoms, 832 splited: list, labels: list[str])\ 833 -> CoordinatesOfAtoms: 834 i = 0 835 for item in splited: 836 if i == 0: 837 atom_id = int(item) 838 elif i == 1: 839 atom_symbol = item 840 elif labels[i - 2] == cls._valid_coordinatios_columns_names[0]: 841 x = float(item) 842 elif labels[i - 2] == cls._valid_coordinatios_columns_names[1]: 843 y = float(item) 844 elif labels[i - 2] == cls._valid_coordinatios_columns_names[2]: 845 z = float(item) 846 i += 1 847 848 coordinates_of_atoms.add_new_atom(atom_id, atom_symbol, (x, y, z)) 849 return coordinates_of_atoms 850 851 @staticmethod 852 def _get_rows_of_data_from_file(file: str, 853 finger_print_begin: str, finger_print_end)\ 854 -> list[str] | None: 855 """Get rows fo data from file 856 857 Args: 858 finger_print_begin (str): fingerprint on beginning of data 859 finger_print_end (str): fingerprint on end of data 860 861 Returns: 862 **list[str]**: rows of string data 863 864 """ 865 866 regex = f'(?<={finger_print_begin})[\s\S]*' \ 867 + f'(?={finger_print_end})' 868 869 match = re.search(regex, file) 870 if match is not None: 871 return match[0].split('\n') 872 else: 873 return None 874 875 def _load_mayer_bond_orders(self, file: str) -> MayerBondOrders: 876 match = re.search( 877 f"(?<={self._fingerprint_mayer_bond_orders}\n\n)([\S ]+\n)*", file) 878 if match is None: 879 raise Exception("Mayers bond orders aren't in file or wrong file" 880 + "format!!!!") 881 rows = match[0].split('\n') 882 rows = [row for row in rows if row != ''] 883 number_of_atoms = int(rows[-1].split()[0]) 884 n = number_of_atoms // 8 885 m = 1 if number_of_atoms % 8 > 0 else 0 886 number_of_tables = m + n 887 888 number_of_rows = (len(rows)) * number_of_tables 889 match = re.search( 890 f"(?<={self._fingerprint_mayer_bond_orders}\n\n)([\S ]+\n*){{{number_of_rows}}}", file) 891 892 if match is None: 893 raise Exception("Mayers bond orders aren't in file or wrong file" 894 + "format!!!!") 895 rows_full_table = [] 896 tables = match[0].split('\n\n') 897 first_table = True 898 for table in tables: 899 splited = table.split('\n') 900 i = 0 901 for item in splited: 902 if first_table: 903 row = item.split() 904 rows_full_table.append(row) 905 elif i == 0: 906 row = item.split() 907 rows_full_table[i].extend(row) 908 else: 909 row = item.split() 910 rows_full_table[i].extend(row[2:]) 911 i += 1 912 first_table = False 913 914 row_horizontal = rows_full_table.pop(0) 915 i = 0 916 j = 0 917 horizontal_atom_symbol = {} 918 horizontal_atom_id = [] 919 for item in row_horizontal: 920 if i % 2 == 0: 921 j += 1 922 horizontal_atom_id.append(int(item)) 923 else: 924 horizontal_atom_symbol.update( 925 {horizontal_atom_id[j - 1]: str(item)}) 926 i += 1 927 928 vertical_atom_symbol = {} 929 vertical_atom_id = [] 930 new_rows_full_table = [] 931 for row in rows_full_table: 932 vertical_atom_id.append(int(row.pop(0))) 933 vertical_atom_symbol.update( 934 {vertical_atom_id[-1]: str(row.pop(0))}) 935 row_f = [float(item) for item in row] 936 new_rows_full_table.append(row_f) 937 938 if horizontal_atom_id == vertical_atom_id: 939 mayer_bond_order = self.MayerBondOrders(new_rows_full_table, 940 horizontal_atom_id, 941 horizontal_atom_symbol, 942 vertical_atom_symbol) 943 else: 944 raise Exception( 945 "The condition: 'horizontal_atom_id == vertical_atom_id' " 946 + "must be met ") 947 return mayer_bond_order 948 949 @classmethod 950 def _load_unit_cell(cls, file: str): 951 vectors = [] 952 for item in cls._fingerprints_unit_cell: 953 regex = f'(?<={item})[ \S]+' 954 match = re.search(regex, file) 955 if match is None: 956 raise Exception(" wrong file format!!!!") 957 row = match[0].split() 958 row_f = [float(item) for item in row] 959 vectors.append(row_f) 960 unit_cell = UnitCell() 961 unit_cell.lattice_vectors = tuple(item 962 for item in vectors) 963 return unit_cell
Loads data from CPMD file and create objects.
697 def load_input_data(self, path: str, *args) -> None: 698 """Loads input data from CPMD file. 699 700 Args: 701 path (str): path to file 702 *args: LoadData.UnitCell, LoadedData.MayerBondOrders, 703 LoadData.Populations or LoadData.CoordinatesOfAtoms 704 705 """ 706 with open(path, 'r') as file: 707 data = file.read() 708 709 if self.check_is_correct_file(data, self._fingerprint) is not True: 710 raise Exception("Wrong input file!") 711 else: 712 if self.LoadedData.UnitCell in args: 713 self.unit_cell = self._load_unit_cell(data) 714 if self.LoadedData.MayerBondOrders in args: 715 self.mayer_bond_orders = \ 716 self._load_mayer_bond_orders(data) 717 if self.LoadedData.Populations in args: 718 self.populations = self._load_populations(data) 719 if self.LoadedData.CoordinatesOfAtoms in args: 720 self.coordinates_of_atoms = \ 721 self._load_coordinates_of_atoms(data)
Loads input data from CPMD file.
Arguments:
- path (str): path to file
- *args: LoadData.UnitCell, LoadedData.MayerBondOrders,
- LoadData.Populations or LoadData.CoordinatesOfAtoms
723 def return_data(self, loaded_data: LoadedData)\ 724 -> Populations | UnitCell | MayerBondOrders | CoordinatesOfAtoms\ 725 | None: 726 """ Returns loaded data. 727 728 Args: 729 730 Returns: 731 **Populations | UnitCell | MayerBondOrders | CoordinatesOfAtoms**: 732 733 """ 734 if loaded_data is LoadedData.UnitCell: 735 return self.unit_cell 736 elif loaded_data is LoadedData.MayerBondOrders: 737 return self.mayer_bond_orders 738 elif loaded_data is LoadedData.Populations: 739 return self.populations 740 elif loaded_data is LoadedData.CoordinatesOfAtoms: 741 return self.coordinates_of_atoms 742 else: 743 raise TypeError("Type is not LoadedData!!!!")
Returns loaded data.
Args:
Returns:
Populations | UnitCell | MayerBondOrders | CoordinatesOfAtoms: