diff --git a/src/py4vasp/_calculation/_CONTCAR.py b/src/py4vasp/_calculation/_CONTCAR.py index 0da5510a..0a631a9d 100644 --- a/src/py4vasp/_calculation/_CONTCAR.py +++ b/src/py4vasp/_calculation/_CONTCAR.py @@ -62,7 +62,7 @@ def _line_generator(self): selective_dynamics = self._raw_data.selective_dynamics yield convert.text_to_string(self._raw_data.system) yield from _cell_lines(cell) - yield self._topology().to_POSCAR() + yield self._stoichiometry().to_POSCAR() if not selective_dynamics.is_none(): yield "Selective dynamics" yield "Direct" @@ -70,8 +70,10 @@ def _line_generator(self): yield from _lattice_velocity_lines(self._raw_data.lattice_velocities, cell) yield from _ion_velocity_lines(self._raw_data.ion_velocities) - def _topology(self): - return calculation._topology.from_data(self._raw_data.structure.topology) + def _stoichiometry(self): + return calculation._stoichiometry.from_data( + self._raw_data.structure.stoichiometry + ) def _cell_lines(cell): diff --git a/src/py4vasp/_calculation/__init__.py b/src/py4vasp/_calculation/__init__.py index 9e0d94cd..9c1cc68d 100644 --- a/src/py4vasp/_calculation/__init__.py +++ b/src/py4vasp/_calculation/__init__.py @@ -77,7 +77,7 @@ class provides a more flexible interface with which you can determine the source "workfunction", "_CONTCAR", "_dispersion", - "_topology", + "_stoichiometry", ) diff --git a/src/py4vasp/_calculation/_topology.py b/src/py4vasp/_calculation/_stoichiometry.py similarity index 84% rename from src/py4vasp/_calculation/_topology.py rename to src/py4vasp/_calculation/_stoichiometry.py index 6ba66af8..7df83671 100644 --- a/src/py4vasp/_calculation/_topology.py +++ b/src/py4vasp/_calculation/_stoichiometry.py @@ -15,21 +15,13 @@ _subscript = "_" -class Topology(base.Refinery): - """The topology of the crystal describes the ions of a crystal and their connectivity. - - At the current stage, this class only exposes the name of the atoms in the unit - cell. In the future, we could add functionality for the user to group multiple - atoms. If you are interested in this feature and have a specific use case in mind, - please create an issue on Github_. - - .. _Github: https://github.com/vasp-dev/py4vasp - """ +class Stoichiometry(base.Refinery): + """The stoichiometry of the crystal describes how many ions of each type exist in a crystal.""" @classmethod def from_ase(cls, structure): - """Generate a Topology from the given ase Atoms object.""" - return cls.from_data(raw_topology_from_ase(structure)) + """Generate a stoichiometry from the given ase Atoms object.""" + return cls.from_data(raw_stoichiometry_from_ase(structure)) @base.data_access def __str__(self): @@ -43,7 +35,7 @@ def _repr_html_(self): @base.data_access def to_dict(self): - """Read the topology and convert it to a dictionary. + """Read the stoichiometry and convert it to a dictionary. Returns ------- @@ -59,7 +51,7 @@ def to_dict(self): @base.data_access def to_frame(self): - """Convert the topology to a DataFrame + """Convert the stoichiometry to a DataFrame Returns ------- @@ -70,7 +62,7 @@ def to_frame(self): @base.data_access def to_mdtraj(self): - """Convert the topology to a mdtraj.Topology.""" + """Convert the stoichiometry to a mdtraj.Topology.""" df = self.to_frame() df["serial"] = None df["resSeq"] = 0 @@ -80,7 +72,7 @@ def to_mdtraj(self): @base.data_access def to_POSCAR(self, format_newline=""): - """Generate the topology lines for the POSCAR file. + """Generate the stoichiometry lines for the POSCAR file. Parameters ---------- @@ -156,8 +148,8 @@ def _ion_types(self): return (clean_string(ion_type) for ion_type in self._raw_data.ion_types) -def raw_topology_from_ase(structure): - """Convert the given ase Atoms object to a raw.Topology.""" +def raw_stoichiometry_from_ase(structure): + """Convert the given ase Atoms object to a raw.Stoichiometry.""" number_ion_types = [] ion_types = [] for element in structure.symbols: @@ -166,7 +158,7 @@ def raw_topology_from_ase(structure): else: ion_types.append(element) number_ion_types.append(1) - return raw.Topology(number_ion_types, ion_types) + return raw.Stoichiometry(number_ion_types, ion_types) def _merge_to_slice_if_possible(selections): diff --git a/src/py4vasp/_calculation/density.py b/src/py4vasp/_calculation/density.py index 0bba38cd..f7dddfb1 100644 --- a/src/py4vasp/_calculation/density.py +++ b/src/py4vasp/_calculation/density.py @@ -83,7 +83,8 @@ class Density(base.Refinery, structure.Mixin, view.Mixin): def __str__(self): _raise_error_if_no_data(self._raw_data.charge) grid = self._raw_data.charge.shape[1:] - topology = calculation._topology.from_data(self._raw_data.structure.topology) + raw_stoichiometry = self._raw_data.structure.stoichiometry + stoichiometry = calculation._stoichiometry.from_data(raw_stoichiometry) if self._selection == "kinetic_energy": name = "Kinetic energy" elif self.is_nonpolarized(): @@ -93,7 +94,7 @@ def __str__(self): else: name = "Noncollinear" return f"""{name} density: - structure: {pretty.pretty(topology)} + structure: {pretty.pretty(stoichiometry)} grid: {grid[2]}, {grid[1]}, {grid[0]}""" @documentation.format( diff --git a/src/py4vasp/_calculation/partial_density.py b/src/py4vasp/_calculation/partial_density.py index 44a9ebc1..4867d7be 100644 --- a/src/py4vasp/_calculation/partial_density.py +++ b/src/py4vasp/_calculation/partial_density.py @@ -74,7 +74,7 @@ def stm_settings(self): def __str__(self): """Return a string representation of the partial charge density.""" return f""" - {"spin polarized" if self._spin_polarized() else ""} partial charge density of {self._topology()}: + {"spin polarized" if self._spin_polarized() else ""} partial charge density of {self._stoichiometry()}: on fine FFT grid: {self.grid()} {"summed over all contributing bands" if 0 in self.bands() else f" separated for bands: {self.bands()}"} {"summed over all contributing k-points" if 0 in self.kpoints() else f" separated for k-points: {self.kpoints()}"} @@ -208,16 +208,16 @@ def _constant_current_stm(self, smoothed_charge, current, spin, stm_settings): scan = z_grid[np.argmax(splines(z_grid) >= current, axis=-1)] scan = z_step * (scan - scan.min()) spin_label = "both spin channels" if spin == "total" else f"spin {spin}" - topology = self._topology() - label = f"STM of {topology} for {spin_label} at constant current={current*1e9:.2f} nA" + stoichiometry = self._stoichiometry() + label = f"STM of {stoichiometry} for {spin_label} at constant current={current*1e9:.2f} nA" return Contour(data=scan, lattice=self._get_stm_plane(), label=label) def _constant_height_stm(self, smoothed_charge, tip_height, spin, stm_settings): zz = self._z_index_for_height(tip_height + self._get_highest_z_coord()) height_scan = smoothed_charge[:, :, zz] * stm_settings.enhancement_factor spin_label = "both spin channels" if spin == "total" else f"spin {spin}" - topology = self._topology() - label = f"STM of {topology} for {spin_label} at constant height={float(tip_height):.2f} Angstrom" + stoichiometry = self._stoichiometry() + label = f"STM of {stoichiometry} for {spin_label} at constant height={float(tip_height):.2f} Angstrom" return Contour(data=height_scan, lattice=self._get_stm_plane(), label=label) def _z_index_for_height(self, tip_height): @@ -242,8 +242,8 @@ def _get_lowest_z_coord(self): cart_coords = _get_sanitized_cartesian_positions(self._structure) return np.min(cart_coords[:, 2]) - def _topology(self): - return str(self._structure._topology()) + def _stoichiometry(self): + return str(self._structure._stoichiometry()) def _estimate_vacuum(self): _raise_error_if_vacuum_not_along_z(self._structure) diff --git a/src/py4vasp/_calculation/phonon.py b/src/py4vasp/_calculation/phonon.py index 522eb85d..c1936561 100644 --- a/src/py4vasp/_calculation/phonon.py +++ b/src/py4vasp/_calculation/phonon.py @@ -36,13 +36,13 @@ def selections(self): "direction": ["x", "y", "z"], } - def _topology(self): - return calculation._topology.from_data(self._raw_data.topology) + def _stoichiometry(self): + return calculation._stoichiometry.from_data(self._raw_data.stoichiometry) def _init_atom_dict(self): return { key: value.indices - for key, value in self._topology().read().items() + for key, value in self._stoichiometry().read().items() if key != select.all } diff --git a/src/py4vasp/_calculation/phonon_band.py b/src/py4vasp/_calculation/phonon_band.py index 9a5d4017..284d3980 100644 --- a/src/py4vasp/_calculation/phonon_band.py +++ b/src/py4vasp/_calculation/phonon_band.py @@ -31,7 +31,7 @@ def __str__(self): return f"""phonon band data: {self._raw_data.dispersion.eigenvalues.shape[0]} q-points {self._raw_data.dispersion.eigenvalues.shape[1]} modes - {self._topology()}""" + {self._stoichiometry()}""" @base.data_access def to_dict(self): diff --git a/src/py4vasp/_calculation/phonon_dos.py b/src/py4vasp/_calculation/phonon_dos.py index 289e2a63..1ddb6cca 100644 --- a/src/py4vasp/_calculation/phonon_dos.py +++ b/src/py4vasp/_calculation/phonon_dos.py @@ -26,11 +26,11 @@ class PhononDos(phonon.Mixin, base.Refinery, graph.Mixin): @base.data_access def __str__(self): energies = self._raw_data.energies - topology = self._topology() + stoichiometry = self._stoichiometry() return f"""phonon DOS: [{energies[0]:0.2f}, {energies[-1]:0.2f}] mesh with {len(energies)} points - {3 * topology.number_atoms()} modes - {topology}""" + {3 * stoichiometry.number_atoms()} modes + {stoichiometry}""" @base.data_access @documentation.format(selection=phonon.selection_doc) diff --git a/src/py4vasp/_calculation/potential.py b/src/py4vasp/_calculation/potential.py index a6cdeb4e..e1af553c 100644 --- a/src/py4vasp/_calculation/potential.py +++ b/src/py4vasp/_calculation/potential.py @@ -39,8 +39,10 @@ def __str__(self): description = "noncollinear potential:" else: description = "nonpolarized potential:" - topology = calculation._topology.from_data(self._raw_data.structure.topology) - structure = f"structure: {topology}" + stoichiometry = calculation._stoichiometry.from_data( + self._raw_data.structure.stoichiometry + ) + structure = f"structure: {stoichiometry}" grid = f"grid: {potential.shape[3]}, {potential.shape[2]}, {potential.shape[1]}" available = "available: " + ", ".join( kind for kind in VALID_KINDS if not self._get_potential(kind).is_none() diff --git a/src/py4vasp/_calculation/projector.py b/src/py4vasp/_calculation/projector.py index 01367e00..9ac8f40e 100644 --- a/src/py4vasp/_calculation/projector.py +++ b/src/py4vasp/_calculation/projector.py @@ -84,7 +84,7 @@ def __str__(self): if self._raw_data.orbital_types.is_none(): return "no projectors" return f"""projectors: - atoms: {", ".join(self._topology().ion_types())} + atoms: {", ".join(self._stoichiometry().ion_types())} orbitals: {", ".join(self._orbital_types())}""" @base.data_access @@ -190,8 +190,8 @@ def _raise_error_if_orbitals_missing(self): message = "Projectors are not available, rerun Vasp setting LORBIT >= 10." raise exception.IncorrectUsage(message) - def _topology(self): - return calculation._topology.from_data(self._raw_data.topology) + def _stoichiometry(self): + return calculation._stoichiometry.from_data(self._raw_data.stoichiometry) def _init_dicts(self): if self._raw_data.orbital_types.is_none(): @@ -204,7 +204,7 @@ def _init_dicts(self): def _init_atom_dict(self): return { key: value.indices - for key, value in self._topology().read().items() + for key, value in self._stoichiometry().read().items() if key != _select_all } @@ -347,7 +347,7 @@ def _init_dicts_old(self): } def _init_atom_dict_old(self): - return self._topology().read() + return self._stoichiometry().read() def _init_orbital_dict_old(self): self._raise_error_if_orbitals_missing() diff --git a/src/py4vasp/_calculation/structure.py b/src/py4vasp/_calculation/structure.py index 18aeed53..2b3d101d 100644 --- a/src/py4vasp/_calculation/structure.py +++ b/src/py4vasp/_calculation/structure.py @@ -6,7 +6,7 @@ import numpy as np from py4vasp import calculation, exception, raw -from py4vasp._calculation import _topology, base, slice_ +from py4vasp._calculation import _stoichiometry, base, slice_ from py4vasp._third_party import view from py4vasp._util import documentation, import_, reader @@ -23,14 +23,14 @@ class _Format: end_table: str = "" newline: str = "" - def comment_line(self, topology, step_string): - return f"{topology}{step_string}{self.newline}" + def comment_line(self, stoichiometry, step_string): + return f"{stoichiometry}{step_string}{self.newline}" def scaling_factor(self, scale): return f"{self._element_to_string(scale)}{self.newline}".lstrip() - def ion_list(self, topology): - return f"{topology.to_POSCAR(self.newline)}{self.newline}" + def ion_list(self, stoichiometry): + return f"{stoichiometry.to_POSCAR(self.newline)}{self.newline}" def coordinate_system(self): return f"Direct{self.newline}" @@ -95,7 +95,7 @@ def from_POSCAR(cls, poscar, *, elements=None): def from_ase(cls, structure): """Generate a structure from the ase Atoms class.""" structure = raw.Structure( - topology=_topology.raw_topology_from_ase(structure), + stoichiometry=_stoichiometry.raw_stoichiometry_from_ase(structure), cell=_cell_from_ase(structure), positions=structure.get_scaled_positions()[np.newaxis], ) @@ -120,10 +120,10 @@ def _repr_html_(self): def _create_repr(self, format_=_Format()): step = self._get_last_step() lines = ( - format_.comment_line(self._topology(), self._step_string()), + format_.comment_line(self._stoichiometry(), self._step_string()), format_.scaling_factor(self._scale()), format_.vectors_to_table(self._raw_data.cell.lattice_vectors[step]), - format_.ion_list(self._topology()), + format_.ion_list(self._stoichiometry()), format_.coordinate_system(), format_.vectors_to_table(self._raw_data.positions[step]), ) @@ -146,8 +146,8 @@ def to_dict(self): return { "lattice_vectors": self.lattice_vectors(), "positions": self.positions(), - "elements": self._topology().elements(), - "names": self._topology().names(), + "elements": self._stoichiometry().elements(), + "names": self._stoichiometry().names(), } @base.data_access @@ -170,7 +170,7 @@ def to_view(self, supercell=None): """ make_3d = lambda array: array if array.ndim == 3 else array[np.newaxis] positions = make_3d(self.positions()) - elements = np.tile(self._topology().elements(), (len(positions), 1)) + elements = np.tile(self._stoichiometry().elements(), (len(positions), 1)) return view.View( elements=elements, lattice_vectors=make_3d(self.lattice_vectors()), @@ -241,7 +241,7 @@ def to_mdtraj(self): raise exception.NotImplemented(message) data = self.to_dict() xyz = data["positions"] @ data["lattice_vectors"] * self.A_to_nm - trajectory = mdtraj.Trajectory(xyz, self._topology().to_mdtraj()) + trajectory = mdtraj.Trajectory(xyz, self._stoichiometry().to_mdtraj()) trajectory.unitcell_vectors = data["lattice_vectors"] * Structure.A_to_nm return trajectory @@ -356,8 +356,8 @@ def _parse_supercell(self, supercell): ) raise exception.IncorrectUsage(message) - def _topology(self): - return calculation._topology.from_data(self._raw_data.topology) + def _stoichiometry(self): + return calculation._stoichiometry.from_data(self._raw_data.stoichiometry) def _scale(self): if isinstance(self._raw_data.cell.scale, np.float64): diff --git a/src/py4vasp/_raw/data.py b/src/py4vasp/_raw/data.py index db992385..51ab8fa9 100644 --- a/src/py4vasp/_raw/data.py +++ b/src/py4vasp/_raw/data.py @@ -90,7 +90,7 @@ class Cell: class CONTCAR: """The data corresponding to the CONTCAR file. - The CONTCAR file contains structural information (lattice, positions, topology), + The CONTCAR file contains structural information (lattice, positions, stoichiometry), relaxation constraints, and data relevant for continuation calculations. """ @@ -366,11 +366,11 @@ class PhononBand: """The band structure of the phonons. Contains the eigenvalues and eigenvectors at specifics **q** points in the Brillouin - zone. Includes the topology to map atoms onto specific modes.""" + zone. Includes the stoichiometry to map atoms onto specific modes.""" dispersion: Dispersion "The **q** points and the eigenvalues." - topology: Topology + stoichiometry: Stoichiometry "The atom types in the crystal." eigenvectors: VaspData "The eigenvectors of the phonon modes." @@ -389,7 +389,7 @@ class PhononDos: "Dos at the energies D(E)." projections: VaspData "Projection of the DOS onto contribution of specific atoms." - topology: Topology + stoichiometry: Stoichiometry "The atom types in the crystal." @@ -451,14 +451,23 @@ class Projector: and orbitals. This class reports the atoms and orbitals included in the projection. """ - topology: Topology - "The topology of the system used, i.e., which elements are contained." + stoichiometry: Stoichiometry + "The stoichiometry of the system used, i.e., which elements are contained." orbital_types: VaspData "Character indicating the orbital angular momentum." number_spins: int "Indicates whether the calculation is spin polarized or not." +@dataclasses.dataclass +class Stoichiometry: + "Contains the type of ions in the system and how many of each type exist." + number_ion_types: VaspData + "Amount of ions of a particular type." + ion_types: VaspData + "Element of a particular type." + + @dataclasses.dataclass class Stress: "The stress acting on the unit cell at all steps." @@ -475,8 +484,8 @@ class Structure: Reports what ions are in the system and the positions of all ions as well as the unit cell for all steps in a relaxation in a MD run.""" - topology: Topology - "The topology of the system used, i.e., which elements are contained." + stoichiometry: Stoichiometry + "The stoichiometry of the system used, i.e., which elements are contained." cell: Cell "Unit cell of the crystal or simulation cell for molecules." positions: VaspData @@ -489,15 +498,6 @@ class System: system: str -@dataclasses.dataclass -class Topology: - "Contains the type of ions in the system and how many of each type exist." - number_ion_types: VaspData - "Amount of ions of a particular type." - ion_types: VaspData - "Element of a particular type." - - @dataclasses.dataclass class Velocity: "Contains the ion velocities along the trajectory." diff --git a/src/py4vasp/_raw/definition.py b/src/py4vasp/_raw/definition.py index 67f43878..69858485 100644 --- a/src/py4vasp/_raw/definition.py +++ b/src/py4vasp/_raw/definition.py @@ -410,7 +410,7 @@ def selections(quantity): raw.PhononBand, required=raw.Version(6, 4), dispersion=Link("dispersion", "phonon"), - topology=Link("topology", "phonon"), + stoichiometry=Link("stoichiometry", "phonon"), eigenvectors=f"{group}/eigenvectors", ) schema.add( @@ -418,7 +418,7 @@ def selections(quantity): required=raw.Version(6, 4), energies=f"{group}/dos_mesh", dos=f"{group}/dos", - topology=Link("topology", "phonon"), + stoichiometry=Link("stoichiometry", "phonon"), projections=f"{group}/dospar", ) # @@ -450,25 +450,38 @@ def selections(quantity): # schema.add( raw.Projector, - topology=Link("topology", DEFAULT_SOURCE), + stoichiometry=Link("stoichiometry", DEFAULT_SOURCE), orbital_types="results/projectors/lchar", number_spins=Length("results/electron_eigenvalues/eigenvalues"), ) schema.add( raw.Projector, name="kpoints_opt", - topology=Link("topology", DEFAULT_SOURCE), + stoichiometry=Link("stoichiometry", DEFAULT_SOURCE), orbital_types="results/projectors_kpoints_opt/lchar", number_spins=Length("results/electron_eigenvalues/eigenvalues"), ) schema.add( raw.Projector, name="kpoints_wan", - topology=Link("topology", DEFAULT_SOURCE), + stoichiometry=Link("stoichiometry", DEFAULT_SOURCE), orbital_types="results/projectors_kpoints_wan/lchar", number_spins=Length("results/electron_eigenvalues/eigenvalues"), ) # +schema.add( + raw.Stoichiometry, + ion_types="results/positions/ion_types", + number_ion_types="results/positions/number_ion_types", +) +schema.add( + raw.Stoichiometry, + name="phonon", + required=raw.Version(6, 4), + ion_types="results/phonons/primitive/ion_types", + number_ion_types="results/phonons/primitive/number_ion_types", +) +# schema.add( raw.Stress, structure=Link("structure", DEFAULT_SOURCE), @@ -477,7 +490,7 @@ def selections(quantity): # schema.add( raw.Structure, - topology=Link("topology", DEFAULT_SOURCE), + stoichiometry=Link("stoichiometry", DEFAULT_SOURCE), cell=Link("cell", DEFAULT_SOURCE), positions="intermediate/ion_dynamics/position_ions", ) @@ -485,26 +498,13 @@ def selections(quantity): raw.Structure, name="final", required=raw.Version(6, 5), - topology=Link("topology", DEFAULT_SOURCE), + stoichiometry=Link("stoichiometry", DEFAULT_SOURCE), cell=Link("cell", "final"), positions="results/positions/position_ions", ) # schema.add(raw.System, system="input/incar/SYSTEM") # -schema.add( - raw.Topology, - ion_types="results/positions/ion_types", - number_ion_types="results/positions/number_ion_types", -) -schema.add( - raw.Topology, - name="phonon", - required=raw.Version(6, 4), - ion_types="results/phonons/primitive/ion_types", - number_ion_types="results/phonons/primitive/number_ion_types", -) -# schema.add( raw.Velocity, required=raw.Version(6, 4), diff --git a/src/py4vasp/_util/parser.py b/src/py4vasp/_util/parser.py index 586e9ce9..976f5579 100644 --- a/src/py4vasp/_util/parser.py +++ b/src/py4vasp/_util/parser.py @@ -3,7 +3,7 @@ import numpy as np -from py4vasp._raw.data import CONTCAR, Cell, Structure, Topology +from py4vasp._raw.data import CONTCAR, Cell, Structure, Stoichiometry from py4vasp._raw.data_wrapper import VaspData from py4vasp.exception import ParserError @@ -114,10 +114,10 @@ def has_selective_dynamics(self): return False @property - def topology(self): - """The topology from the POSCAR file. + def stoichiometry(self): + """The stoichiometry from the POSCAR file. - Parses the topology from the POSCAR file. The topology is parsed as is + Parses the stoichiometry from the POSCAR file. The stoichiometry is parsed as is and the species names are reported in the Topology object. If the species names are not specified in the POSCAR file, then the species names must be supplied as an argument. @@ -134,8 +134,7 @@ def topology(self): number_of_species = self.split_poscar[5].split() number_of_species = VaspData(np.array(number_of_species, dtype=int)) species_name = VaspData(np.array(species_name)) - topology = Topology(number_ion_types=number_of_species, ion_types=species_name) - return topology + return Stoichiometry(number_ion_types=number_of_species, ion_types=species_name) @property def ion_positions_and_selective_dynamics(self): @@ -147,7 +146,7 @@ def ion_positions_and_selective_dynamics(self): specified in Cartesian coordinates, then the positions are converted to direct coordinates. """ - number_of_species = self.topology.number_ion_types.data.sum() + number_of_species = self.stoichiometry.number_ion_types.data.sum() idx_start = 6 if self.has_selective_dynamics: idx_start += 1 @@ -196,7 +195,7 @@ def has_lattice_velocities(self): is 'Lattice velocities and vectors', then it is assumed that the POSCAR file has lattice velocities. """ - num_species = self.topology.number_ion_types.data.sum() + num_species = self.stoichiometry.number_ion_types.data.sum() idx_start = 7 + num_species if self.has_selective_dynamics: idx_start += 1 @@ -219,7 +218,7 @@ def lattice_velocities(self): If the velocities are specified in Direct coordinates, then the velocities are converted to Cartesian coordinates. """ - num_species = self.topology.number_ion_types.data.sum() + num_species = self.stoichiometry.number_ion_types.data.sum() idx_start = 7 + num_species if not self.has_lattice_velocities: raise ParserError("No lattice velocities found in POSCAR.") @@ -251,7 +250,7 @@ def has_ion_velocities(self): (assumed to be Cartesian). If the header is not one of these, then it is assumed that the POSCAR file does not have ion velocities. """ - num_species = self.topology.number_ion_types.data.sum() + num_species = self.stoichiometry.number_ion_types.data.sum() idx_start = 7 + num_species if self.has_selective_dynamics: idx_start += 1 @@ -276,7 +275,7 @@ def ion_velocities(self): If the velocities are specified in Direct coordinates, then the velocities are converted to Cartesian coordinates. """ - num_species = self.topology.number_ion_types.data.sum() + num_species = self.stoichiometry.number_ion_types.data.sum() if not self.has_ion_velocities: raise ParserError("No ion velocities found in POSCAR.") idx_start = 7 + num_species @@ -307,7 +306,7 @@ def to_contcar(self): """ ion_positions, selective_dynamics = self.ion_positions_and_selective_dynamics structure = Structure( - topology=self.topology, + stoichiometry=self.stoichiometry, cell=self.cell, positions=ion_positions, ) diff --git a/src/py4vasp/calculation/__init__.py b/src/py4vasp/calculation/__init__.py deleted file mode 100644 index 17a4ae34..00000000 --- a/src/py4vasp/calculation/__init__.py +++ /dev/null @@ -1,98 +0,0 @@ -# Copyright © VASP Software GmbH, -# Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -"""Provide refinement functions for a the raw data of a VASP calculation run in the -current directory. - -Usually one is not directly interested in the raw data that is produced but -wants to produce either a figure for a publication or some post-processing of -the data. This package contains multiple modules that enable these kinds of -workflows by extracting the relevant data from the HDF5 file and transforming -them into an accessible format. The modules also provide plotting functionality -to get a quick insight about the data, which can then be refined either within -python or a different tool to obtain publication-quality figures. - -Generally, all modules provide a `read` function that extracts the data from the -HDF5 file and puts it into a Python dictionary. Where it makes sense in addition -a `plot` function is available that converts the data into a figure for Jupyter -notebooks. In addition, data conversion routines `to_X` may be available -transforming the data into another format or file, which may be useful to -generate plots with tools other than Python. For the specifics, please refer to -the documentation of the individual modules. - -The raw data is read from the current directory. The :class:`~py4vasp.Calculation` -class provides a more flexible interface with which you can determine the source -directory or file for the VASP calculation manually. That class exposes functions -of the modules as methods of attributes, i.e., the two following examples are -equivalent: - -.. rubric:: using :mod:`~py4vasp.calculation` module - ->>> from py4vasp import calculation ->>> calculation.dos.read() - -.. rubric:: using :class:`~py4vasp.Calculation` class - ->>> from py4vasp import Calculation ->>> calc = Calculation.from_path(".") ->>> calc.dos.read() - -In the latter example, you can change the path from which the data is extracted. -""" -import importlib -import pathlib - -from py4vasp import control, exception -from py4vasp._util import convert - -_input_files = ("INCAR", "KPOINTS", "POSCAR") -_quantities = ( - "band", - "bandgap", - "born_effective_charge", - "CONTCAR", - "density", - "dielectric_function", - "dielectric_tensor", - "dos", - "elastic_modulus", - "energy", - "fatband", - "force", - "force_constant", - "internal_strain", - "kpoint", - "magnetism", - "electronic_minimization", - "pair_correlation", - "partial_charge", - "phonon_band", - "phonon_dos", - "piezoelectric_tensor", - "polarization", - "potential", - "projector", - "stress", - "structure", - "system", - "topology", - "velocity", - "workfunction", -) -_private = ("dispersion",) -__all__ = _quantities # + _input_files - - -path = pathlib.Path(".") - - -def __getattr__(attr): - if attr in (_quantities + _private): - module = importlib.import_module(f"py4vasp.calculation._{attr}") - class_ = getattr(module, convert.to_camelcase(attr)) - return class_.from_path(".") - # elif attr in (_input_files): - # class_ = getattr(control, attr) - # return class_(".") - else: - message = f"Could not find {attr} in the possible attributes, please check the spelling" - raise exception.MissingAttribute(message) diff --git a/tests/calculation/test_partial_density.py b/tests/calculation/test_partial_density.py index 4167a6ac..1dfce5b0 100644 --- a/tests/calculation/test_partial_density.py +++ b/tests/calculation/test_partial_density.py @@ -116,9 +116,9 @@ def test_read(PartialDensity, Assert, not_core): Assert.same_structure(actual["structure"], expected.structure.read()) -def test_topology(PartialDensity, not_core): - actual = PartialDensity._topology() - expected = str(PartialDensity.ref.structure._topology()) +def test_stoichiometry(PartialDensity, not_core): + actual = PartialDensity._stoichiometry() + expected = str(PartialDensity.ref.structure._stoichiometry()) assert actual == expected diff --git a/tests/calculation/test_phonon_band.py b/tests/calculation/test_phonon_band.py index 857c88ac..3f9f0179 100644 --- a/tests/calculation/test_phonon_band.py +++ b/tests/calculation/test_phonon_band.py @@ -19,7 +19,8 @@ def phonon_band(raw_data): band.ref.modes = convert.to_complex(raw_band.eigenvectors) raw_qpoints = raw_band.dispersion.kpoints band.ref.qpoints = calculation.kpoint.from_data(raw_qpoints) - band.ref.topology = calculation._topology.from_data(raw_band.topology) + raw_stoichiometry = raw_band.stoichiometry + band.ref.stoichiometry = calculation._stoichiometry.from_data(raw_stoichiometry) Sr = slice(0, 2) band.ref.Sr = np.sum(np.abs(band.ref.modes[:, :, Sr, :]), axis=(2, 3)) Ti = 2 diff --git a/tests/calculation/test_topology.py b/tests/calculation/test_stoichiometry.py similarity index 55% rename from tests/calculation/test_topology.py rename to tests/calculation/test_stoichiometry.py index be7995b7..30e1aaef 100644 --- a/tests/calculation/test_topology.py +++ b/tests/calculation/test_stoichiometry.py @@ -12,22 +12,22 @@ class Base: def test_read(self): - topology = self.topology.read() - assert topology[select.all] == Selection(indices=slice(0, 7)) + stoichiometry = self.stoichiometry.read() + assert stoichiometry[select.all] == Selection(indices=slice(0, 7)) for i, name in enumerate(self.names): expected = Selection(indices=slice(i, i + 1), label=name) - assert topology[str(i + 1)] == expected - self.check_ion_indices(topology) + assert stoichiometry[str(i + 1)] == expected + self.check_ion_indices(stoichiometry) def test_to_frame(self, not_core): - actual = self.topology.to_frame() + actual = self.stoichiometry.to_frame() ref_data = {"name": self.names, "element": self.elements} reference = pd.DataFrame(ref_data) assert reference.equals(actual) def test_to_mdtraj(self, not_core): - actual, _ = self.topology.to_mdtraj().to_dataframe() - num_atoms = self.topology.number_atoms() + actual, _ = self.stoichiometry.to_mdtraj().to_dataframe() + num_atoms = self.stoichiometry.number_atoms() ref_data = { "serial": num_atoms * (None,), "name": self.names, @@ -41,23 +41,23 @@ def test_to_mdtraj(self, not_core): assert reference.equals(actual) def test_elements(self): - assert self.topology.elements() == self.elements + assert self.stoichiometry.elements() == self.elements def test_ion_types(self): - assert self.topology.ion_types() == self.unique_elements + assert self.stoichiometry.ion_types() == self.unique_elements def test_names(self): - actual = self.topology.names() + actual = self.stoichiometry.names() assert actual == self.names def test_number_atoms(self): - assert self.topology.number_atoms() == 7 + assert self.stoichiometry.number_atoms() == 7 def test_from_ase(self, not_core): structure = ase.Atoms("".join(self.elements)) - topology = calculation._topology.from_ase(structure) - assert topology.elements() == self.elements - assert str(topology) == str(self.topology) + stoichiometry = calculation._stoichiometry.from_ase(structure) + assert stoichiometry.elements() == self.elements + assert str(stoichiometry) == str(self.stoichiometry) @property def unique_elements(self): @@ -71,23 +71,23 @@ def unique_elements(self): class TestSr2TiO4(Base): @pytest.fixture(autouse=True) def _setup(self, raw_data): - self.topology = calculation._topology.from_data(raw_data.topology("Sr2TiO4")) + self.stoichiometry = calculation._stoichiometry.from_data(raw_data.stoichiometry("Sr2TiO4")) self.names = ["Sr_1", "Sr_2", "Ti_1", "O_1", "O_2", "O_3", "O_4"] self.elements = 2 * ["Sr"] + ["Ti"] + 4 * ["O"] - def check_ion_indices(self, topology): - assert topology["Sr"] == Selection(indices=slice(0, 2), label="Sr") - assert topology["Ti"] == Selection(indices=slice(2, 3), label="Ti") - assert topology["O"] == Selection(indices=slice(3, 7), label="O") + def check_ion_indices(self, stoichiometry): + assert stoichiometry["Sr"] == Selection(indices=slice(0, 2), label="Sr") + assert stoichiometry["Ti"] == Selection(indices=slice(2, 3), label="Ti") + assert stoichiometry["O"] == Selection(indices=slice(3, 7), label="O") def test_to_poscar(self): - assert self.topology.to_POSCAR() == "Sr Ti O\n2 1 4" - assert self.topology.to_POSCAR(".format.") == "Sr Ti O.format.\n2 1 4" + assert self.stoichiometry.to_POSCAR() == "Sr Ti O\n2 1 4" + assert self.stoichiometry.to_POSCAR(".format.") == "Sr Ti O.format.\n2 1 4" with pytest.raises(exception.IncorrectUsage): - self.topology.to_POSCAR(None) + self.stoichiometry.to_POSCAR(None) def test_print(self, format_): - actual, _ = format_(self.topology) + actual, _ = format_(self.stoichiometry) reference = { "text/plain": "Sr2TiO4", "text/html": "Sr2TiO4", @@ -100,21 +100,21 @@ class TestCa3AsBr3(Base): @pytest.fixture(autouse=True) def _setup(self, raw_data): - raw_topology = raw_data.topology("Ca2AsBr-CaBr2") - self.topology = calculation._topology.from_data(raw_topology) + raw_stoichiometry = raw_data.stoichiometry("Ca2AsBr-CaBr2") + self.stoichiometry = calculation._stoichiometry.from_data(raw_stoichiometry) self.names = ["Ca_1", "Ca_2", "As_1", "Br_1", "Ca_3", "Br_2", "Br_3"] self.elements = ["Ca", "Ca", "As", "Br", "Ca", "Br", "Br"] - def check_ion_indices(self, topology): - assert topology["Ca"] == Selection(indices=[0, 1, 4], label="Ca") - assert topology["As"] == Selection(indices=slice(2, 3), label="As") - assert topology["Br"] == Selection(indices=[3, 5, 6], label="Br") + def check_ion_indices(self, stoichiometry): + assert stoichiometry["Ca"] == Selection(indices=[0, 1, 4], label="Ca") + assert stoichiometry["As"] == Selection(indices=slice(2, 3), label="As") + assert stoichiometry["Br"] == Selection(indices=[3, 5, 6], label="Br") def test_to_poscar(self): - assert self.topology.to_POSCAR() == "Ca As Br Ca Br\n2 1 1 1 2" + assert self.stoichiometry.to_POSCAR() == "Ca As Br Ca Br\n2 1 1 1 2" def test_print(self, format_): - actual, _ = format_(self.topology) + actual, _ = format_(self.stoichiometry) reference = { "text/plain": "Ca3AsBr3", "text/html": "Ca3AsBr3", @@ -123,5 +123,5 @@ def test_print(self, format_): def test_factory_methods(raw_data, check_factory_methods): - data = raw_data.topology("Sr2TiO4") - check_factory_methods(calculation._topology, data) + data = raw_data.stoichiometry("Sr2TiO4") + check_factory_methods(calculation._stoichiometry, data) diff --git a/tests/calculation/test_structure.py b/tests/calculation/test_structure.py index 9b7761ea..f80a1ca8 100644 --- a/tests/calculation/test_structure.py +++ b/tests/calculation/test_structure.py @@ -97,8 +97,8 @@ def make_structure(raw_structure): scale = 1.0 structure.ref.lattice_vectors = scale * raw_structure.cell.lattice_vectors structure.ref.positions = raw_structure.positions - topology = calculation._topology.from_data(raw_structure.topology) - structure.ref.elements = topology.elements() + stoichiometry = calculation._stoichiometry.from_data(raw_structure.stoichiometry) + structure.ref.elements = stoichiometry.elements() return structure diff --git a/tests/conftest.py b/tests/conftest.py index 97bdacbe..5defef01 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -305,13 +305,13 @@ def structure(selection): raise exception.NotImplemented() @staticmethod - def topology(selection): + def stoichiometry(selection): if selection == "Sr2TiO4": - return _Sr2TiO4_topology() + return _Sr2TiO4_stoichiometry() elif selection == "Fe3O4": - return _Fe3O4_topology() + return _Fe3O4_stoichiometry() elif selection == "Ca2AsBr-CaBr2": # test duplicate entries - return _Ca3AsBr3_topology() + return _Ca3AsBr3_stoichiometry() else: raise exception.NotImplemented() @@ -420,7 +420,7 @@ def _phonon_band(): shape = (*dispersion.eigenvalues.shape, number_atoms, axes, complex_) return raw.PhononBand( dispersion=dispersion, - topology=_Sr2TiO4_topology(), + stoichiometry=_Sr2TiO4_stoichiometry(), eigenvectors=np.linspace(0, 1, np.prod(shape)).reshape(shape), ) @@ -440,7 +440,7 @@ def _phonon_dos(): upper_ratio = np.array(list(reversed(lower_ratio))) ratio = np.linspace(lower_ratio, upper_ratio, number_points).T projections = np.multiply(ratio, dos) - return raw.PhononDos(energies, dos, projections, _Sr2TiO4_topology()) + return raw.PhononDos(energies, dos, projections, _Sr2TiO4_stoichiometry()) def _piezoelectric_tensor(): @@ -836,7 +836,7 @@ def _Sr2TiO4_potential(included_potential): def _Sr2TiO4_projectors(use_orbitals): orbital_types = "s py pz px dxy dyz dz2 dxz x2-y2 fy3x2 fxyz fyz2 fz3 fxz2 fzx2 fx3" return raw.Projector( - topology=_Sr2TiO4_topology(), + stoichiometry=_Sr2TiO4_stoichiometry(), orbital_types=_make_orbital_types(use_orbitals, orbital_types), number_spins=1, ) @@ -866,7 +866,7 @@ def _Graphite_structure(): [0.33333333, 0.66666667, 0.60127716], ] return raw.Structure( - topology=_Graphite_topology(), + stoichiometry=_Graphite_stoichiometry(), cell=_Graphite_cell(), positions=raw.VaspData(positions), ) @@ -881,8 +881,8 @@ def _Graphite_cell(): return raw.Cell(np.asarray(lattice_vectors), scale=raw.VaspData(1.0)) -def _Graphite_topology(): - return raw.Topology( +def _Graphite_stoichiometry(): + return raw.Stoichiometry( number_ion_types=np.array((10,)), ion_types=np.array(("C",), dtype="S"), ) @@ -898,7 +898,7 @@ def _Ni100_structure(): [0.00000000, 0.40000000, 0.00000000], ] return raw.Structure( - topology=_Ni100_topology(), + stoichiometry=_Ni100_stoichiometry(), cell=_Ni100_cell(), positions=raw.VaspData(positions), ) @@ -913,8 +913,8 @@ def _Ni100_cell(): return raw.Cell(np.asarray(lattice_vectors), scale=raw.VaspData(1.0)) -def _Ni100_topology(): - return raw.Topology( +def _Ni100_stoichiometry(): + return raw.Stoichiometry( number_ion_types=np.array((5,)), ion_types=np.array(("Ni",), dtype="S"), ) @@ -949,7 +949,7 @@ def _CaAs3_110_structure(): [0.77964386, 0.09593968, 0.76122779], ] return raw.Structure( - topology=_CaAs3_110_topology(), + stoichiometry=_CaAs3_110_stoichiometry(), cell=_CaAs3_110_cell(), positions=raw.VaspData(positions), ) @@ -964,8 +964,8 @@ def _CaAs3_110_cell(): return raw.Cell(np.asarray(lattice_vectors), scale=raw.VaspData(1.0)) -def _CaAs3_110_topology(): - return raw.Topology( +def _CaAs3_110_stoichiometry(): + return raw.Stoichiometry( number_ion_types=np.array((6, 18)), ion_types=np.array(("Ca", "As"), dtype="S"), ) @@ -983,14 +983,14 @@ def _Sr2TiO4_structure(): [0.00000, 0.50000, 0.5], ] return raw.Structure( - topology=_Sr2TiO4_topology(), + stoichiometry=_Sr2TiO4_stoichiometry(), cell=_Sr2TiO4_cell(), positions=np.tile(positions, repetitions), ) -def _Sr2TiO4_topology(): - return raw.Topology( +def _Sr2TiO4_stoichiometry(): + return raw.Stoichiometry( number_ion_types=np.array((2, 1, 4)), ion_types=np.array(("Sr", "Ti", "O "), dtype="S"), ) @@ -1083,7 +1083,7 @@ def _Fe3O4_potential(selection, included_potential): def _Fe3O4_projectors(use_orbitals): return raw.Projector( - topology=_Fe3O4_topology(), + stoichiometry=_Fe3O4_stoichiometry(), orbital_types=_make_orbital_types(use_orbitals, "s p d f"), number_spins=2, ) @@ -1113,14 +1113,14 @@ def _Fe3O4_structure(): ] shift = np.linspace(-0.02, 0.01, number_steps) return raw.Structure( - topology=_Fe3O4_topology(), + stoichiometry=_Fe3O4_stoichiometry(), cell=_Fe3O4_cell(), positions=np.add.outer(shift, positions), ) -def _Fe3O4_topology(): - return raw.Topology( +def _Fe3O4_stoichiometry(): + return raw.Stoichiometry( number_ion_types=np.array((3, 4)), ion_types=np.array(("Fe", "O "), dtype="S") ) @@ -1149,14 +1149,14 @@ def _Ca3AsBr3_structure(): [0.5, 0.5, 0.0], # Br_3 ] return raw.Structure( - topology=_Ca3AsBr3_topology(), + stoichiometry=_Ca3AsBr3_stoichiometry(), cell=_Ca3AsBr3_cell(), positions=_make_data(positions), ) -def _Ca3AsBr3_topology(): - return raw.Topology( +def _Ca3AsBr3_stoichiometry(): + return raw.Stoichiometry( number_ion_types=np.array((2, 1, 1, 1, 2)), ion_types=np.array(("Ca", "As", "Br", "Ca", "Br"), dtype="S"), ) diff --git a/tests/util/test_parser.py b/tests/util/test_parser.py index 60c47686..49c88b5c 100644 --- a/tests/util/test_parser.py +++ b/tests/util/test_parser.py @@ -6,7 +6,7 @@ import numpy as np import pytest -from py4vasp._raw.data import CONTCAR, Cell, Structure, Topology +from py4vasp._raw.data import CONTCAR, Cell, Structure, Stoichiometry from py4vasp._raw.data_wrapper import VaspData from py4vasp._util.parser import ParsePoscar from py4vasp.exception import ParserError @@ -401,7 +401,7 @@ def test_negative_scaling_factor(cubic_BN, poscar_creator, Assert): @pytest.mark.parametrize("has_species_name", [True, False]) -def test_topology(cubic_BN, has_species_name, Assert): +def test_stoichiometry(cubic_BN, has_species_name, Assert): poscar_string, componentwise_inputs, arguments = cubic_BN( has_species_name=has_species_name ) @@ -411,25 +411,25 @@ def test_topology(cubic_BN, has_species_name, Assert): VaspData(species_names) if species_names else arguments["species_name"].split() ) expected_ions_per_species = VaspData(ions_per_species) - expected_topology = Topology( + expected_stoichiometry = Stoichiometry( number_ion_types=expected_ions_per_species, ion_types=expected_species_names ) - output_topology = ParsePoscar(poscar_string, **arguments).topology + output_stoichiometry = ParsePoscar(poscar_string, **arguments).stoichiometry Assert.allclose( - expected_topology.number_ion_types, output_topology.number_ion_types + expected_stoichiometry.number_ion_types, output_stoichiometry.number_ion_types ) if has_species_name: - expected_ion_types = expected_topology.ion_types.__array__() + expected_ion_types = expected_stoichiometry.ion_types.__array__() else: - expected_ion_types = expected_topology.ion_types - output_ion_types = output_topology.ion_types.__array__() + expected_ion_types = expected_stoichiometry.ion_types + output_ion_types = output_stoichiometry.ion_types.__array__() assert all(expected_ion_types == output_ion_types) def test_error_no_species_provided(cubic_BN): poscar_string, *_ = cubic_BN(has_species_name=False) with pytest.raises(ParserError): - ParsePoscar(poscar_string).topology + ParsePoscar(poscar_string).stoichiometry @pytest.mark.parametrize("has_selective_dynamics", [True, False])