", @@ -120,17 +120,17 @@ 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]), ) return "\n".join(lines) - @_base.data_access - @documentation.format(examples=_slice.examples("structure", "to_dict")) + @base.data_access + @documentation.format(examples=slice_.examples("structure", "to_dict")) def to_dict(self): """Read the structural information into a dictionary. @@ -146,12 +146,12 @@ 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 - @documentation.format(examples=_slice.examples("structure", "to_view")) + @base.data_access + @documentation.format(examples=slice_.examples("structure", "to_view")) def to_view(self, supercell=None): """Generate a 3d representation of the structure(s). @@ -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()), @@ -178,8 +178,8 @@ def to_view(self, supercell=None): supercell=self._parse_supercell(supercell), ) - @_base.data_access - @documentation.format(examples=_slice.examples("structure", "to_ase")) + @base.data_access + @documentation.format(examples=slice_.examples("structure", "to_ase")) def to_ase(self, supercell=None): """Convert the structure to an ase Atoms object. @@ -222,8 +222,8 @@ def to_ase(self, supercell=None): order = sorted(range(num_atoms_super), key=lambda n: n % num_atoms_prim) return structure[order] - @_base.data_access - @documentation.format(examples=_slice.examples("structure", "to_mdtraj")) + @base.data_access + @documentation.format(examples=slice_.examples("structure", "to_mdtraj")) def to_mdtraj(self): """Convert the trajectory to mdtraj.Trajectory @@ -241,12 +241,12 @@ 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 - @_base.data_access - @documentation.format(examples=_slice.examples("structure", "to_POSCAR")) + @base.data_access + @documentation.format(examples=slice_.examples("structure", "to_POSCAR")) def to_POSCAR(self): """Convert the structure(s) to a POSCAR format @@ -263,7 +263,7 @@ def to_POSCAR(self): message = "Converting multiple structures to a POSCAR is currently not implemented." raise exception.NotImplemented(message) - @_base.data_access + @base.data_access def lattice_vectors(self): """Return the lattice vectors spanning the unit cell @@ -275,7 +275,7 @@ def lattice_vectors(self): lattice_vectors = _LatticeVectors(self._raw_data.cell.lattice_vectors) return self._scale() * lattice_vectors[self._get_steps()] - @_base.data_access + @base.data_access def positions(self): """Return the direct coordinates of all ions in the unit cell. @@ -289,8 +289,8 @@ def positions(self): """ return self._raw_data.positions[self._get_steps()] - @_base.data_access - @documentation.format(examples=_slice.examples("structure", "cartesian_positions")) + @base.data_access + @documentation.format(examples=slice_.examples("structure", "cartesian_positions")) def cartesian_positions(self): """Convert the positions from direct coordinates to cartesian ones. @@ -303,8 +303,8 @@ def cartesian_positions(self): """ return self.positions() @ self.lattice_vectors() - @_base.data_access - @documentation.format(examples=_slice.examples("structure", "volume")) + @base.data_access + @documentation.format(examples=slice_.examples("structure", "volume")) def volume(self): """Return the volume of the unit cell for the selected steps. @@ -317,7 +317,7 @@ def volume(self): """ return np.abs(np.linalg.det(self.lattice_vectors())) - @_base.data_access + @base.data_access def number_atoms(self): """Return the total number of atoms in the structure.""" if self._is_trajectory: @@ -325,7 +325,7 @@ def number_atoms(self): else: return self._raw_data.positions.shape[0] - @_base.data_access + @base.data_access def number_steps(self): """Return the number of structures in the trajectory.""" if self._is_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): @@ -382,7 +382,7 @@ def _step_string(self): else: return f" (step {self._steps + 1})" - @_base.data_access + @base.data_access def __getitem__(self, steps): if not self._is_trajectory: message = "The structure is not a Trajectory so accessing individual elements is not allowed." diff --git a/src/py4vasp/calculation/_system.py b/src/py4vasp/_calculation/system.py similarity index 79% rename from src/py4vasp/calculation/_system.py rename to src/py4vasp/_calculation/system.py index 367ee8e6..70e4af04 100644 --- a/src/py4vasp/calculation/_system.py +++ b/src/py4vasp/_calculation/system.py @@ -1,17 +1,17 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +from py4vasp._calculation import base from py4vasp._util import convert -from py4vasp.calculation import _base -class System(_base.Refinery): +class System(base.Refinery): "The :tag:`SYSTEM` tag in the INCAR file is a title you choose for a VASP calculation." - @_base.data_access + @base.data_access def __str__(self): return convert.text_to_string(self._raw_data.system) - @_base.data_access + @base.data_access def to_dict(self): "Returns a dictionary containing the system tag." return {"system": str(self)} diff --git a/src/py4vasp/calculation/_velocity.py b/src/py4vasp/_calculation/velocity.py similarity index 89% rename from src/py4vasp/calculation/_velocity.py rename to src/py4vasp/_calculation/velocity.py index 11945d98..78b9f992 100644 --- a/src/py4vasp/calculation/_velocity.py +++ b/src/py4vasp/_calculation/velocity.py @@ -3,13 +3,13 @@ import numpy as np from py4vasp import _config, exception +from py4vasp._calculation import base, slice_, structure from py4vasp._third_party import view from py4vasp._util import documentation, reader -from py4vasp.calculation import _base, _slice, _structure -@documentation.format(examples=_slice.examples("velocity")) -class Velocity(_slice.Mixin, _base.Refinery, _structure.Mixin, view.Mixin): +@documentation.format(examples=slice_.examples("velocity")) +class Velocity(slice_.Mixin, base.Refinery, structure.Mixin, view.Mixin): """The velocities describe the ionic motion during an MD simulation. The velocities of the ions are a metric for the temperature of the system. Most @@ -27,7 +27,7 @@ class Velocity(_slice.Mixin, _base.Refinery, _structure.Mixin, view.Mixin): velocity_rescale = 200 - @_base.data_access + @base.data_access def __str__(self): step = self._last_step_in_slice velocities = self._vectors_to_string(self._velocity[step]) @@ -42,8 +42,8 @@ def _vector_to_string(self, vector): def _element_to_string(self, element): return f"{element:21.16f}" - @_base.data_access - @documentation.format(examples=_slice.examples("velocity", "to_dict")) + @base.data_access + @documentation.format(examples=slice_.examples("velocity", "to_dict")) def to_dict(self): """Return the structure and ion velocities in a dictionary @@ -60,8 +60,8 @@ def to_dict(self): "velocities": self._velocity[self._steps], } - @_base.data_access - @documentation.format(examples=_slice.examples("velocity", "to_view")) + @base.data_access + @documentation.format(examples=slice_.examples("velocity", "to_view")) def to_view(self, supercell=None): """Plot the velocities as vectors in the structure. diff --git a/src/py4vasp/calculation/_workfunction.py b/src/py4vasp/_calculation/workfunction.py similarity index 82% rename from src/py4vasp/calculation/_workfunction.py rename to src/py4vasp/_calculation/workfunction.py index d6387919..3124845a 100644 --- a/src/py4vasp/calculation/_workfunction.py +++ b/src/py4vasp/_calculation/workfunction.py @@ -1,11 +1,11 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) from py4vasp import calculation +from py4vasp._calculation import base from py4vasp._third_party import graph -from py4vasp.calculation import _base -class Workfunction(_base.Refinery, graph.Mixin): +class Workfunction(base.Refinery, graph.Mixin): """The workfunction describes the energy required to remove an electron to the vacuum. The workfunction of a material is the minimum energy required to remove an @@ -18,17 +18,16 @@ class Workfunction(_base.Refinery, graph.Mixin): resulting potential. """ - @_base.data_access + @base.data_access def __str__(self): data = self.to_dict() return f"""workfunction along {data["direction"]}: vacuum potential: {data["vacuum_potential"][0]:.3f} {data["vacuum_potential"][1]:.3f} - Fermi energy: {data["fermi_energy"]:.3f}""" + Fermi energy: {data["fermi_energy"]:.3f} + valence band maximum: {data["valence_band_maximum"]:.3f} + conduction band minimum: {data["conduction_band_minimum"]:.3f}""" - # valence band maximum: {data["valence_band_maximum"]:.3f} - # conduction band minimum: {data["conduction_band_minimum"]:.3f} - - @_base.data_access + @base.data_access def to_dict(self): """Reports useful information about the workfunction as a dictionary. @@ -44,19 +43,17 @@ def to_dict(self): within the surface. """ bandgap = calculation.bandgap.from_data(self._raw_data.reference_potential) - # vbm and cbm will be uncommented out when the relevant parts of the - # code are added to VASP 6.5 return { "direction": f"lattice vector {self._raw_data.idipol}", "distance": self._raw_data.distance[:], "average_potential": self._raw_data.average_potential[:], "vacuum_potential": self._raw_data.vacuum_potential[:], - # "valence_band_maximum": bandgap.valence_band_maximum(), - # "conduction_band_minimum": bandgap.conduction_band_minimum(), + "valence_band_maximum": bandgap.valence_band_maximum(), + "conduction_band_minimum": bandgap.conduction_band_minimum(), "fermi_energy": self._raw_data.fermi_energy, } - @_base.data_access + @base.data_access def to_graph(self): """Plot the average potential along the lattice vector selected by IDIPOL. diff --git a/src/py4vasp/_combine/base.py b/src/py4vasp/_combine/base.py index 3672a4b5..cabba39a 100644 --- a/src/py4vasp/_combine/base.py +++ b/src/py4vasp/_combine/base.py @@ -4,7 +4,8 @@ import pathlib from typing import Dict, List -from py4vasp import calculation, exception +import py4vasp +from py4vasp import exception def _match_combine_with_refinement(combine_name: str): @@ -13,7 +14,7 @@ def _match_combine_with_refinement(combine_name: str): "Forces": "force", "Stresses": "stress", } - return getattr(calculation, combine_to_refinement_name[combine_name]) + return getattr(py4vasp.calculation, combine_to_refinement_name[combine_name]) # for _, class_ in inspect.getmembers(data_depr, inspect.isclass): # if class_.__name__ == combine_to_refinement_name[combine_name]: # return class_ diff --git a/src/py4vasp/_control/poscar.py b/src/py4vasp/_control/poscar.py index 3816925b..c0ccfdab 100644 --- a/src/py4vasp/_control/poscar.py +++ b/src/py4vasp/_control/poscar.py @@ -1,6 +1,6 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -from py4vasp import calculation +import py4vasp from py4vasp._control import base from py4vasp._third_party import view @@ -36,5 +36,5 @@ def to_view(self, supercell=None, *, elements=None): View Visualize the structure as a 3d figure. """ - structure = calculation.structure.from_POSCAR(self, elements=elements) + structure = py4vasp.calculation.structure.from_POSCAR(self, elements=elements) return structure.plot(supercell) diff --git a/src/py4vasp/_raw/data.py b/src/py4vasp/_raw/data.py index eeee17c5..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. """ @@ -200,6 +200,22 @@ class ElasticModulus: "Elastic modulus when the position of the ions is relaxed." +@dataclasses.dataclass +class ElectronicMinimization: + """The OSZICAR data as generated by VASP. + + All data generated by VASP and traditionally stored in the OSZICAR file will be + stored here. See https://www.vasp.at/wiki/index.php/OSZICAR for more details about + what quantities to expect.""" + + convergence_data: VaspData + "All columns of the OSZICAR file stored for all ionic steps." + label: VaspData + "Label of all the data from the OSZICAR file." + is_elmin_converged: VaspData + "Is the electronic minimization step converged?" + + @dataclasses.dataclass class Energy: """Various energies during ionic relaxation or MD simulation. @@ -312,22 +328,6 @@ class Magnetism: "Contains the orbital magnetization for all atoms" -@dataclasses.dataclass -class ElectronicMinimization: - """The OSZICAR data as generated by VASP. - - All data generated by VASP and traditionally stored in the OSZICAR file will be - stored here. See https://www.vasp.at/wiki/index.php/OSZICAR for more details about - what quantities to expect.""" - - convergence_data: VaspData - "All columns of the OSZICAR file stored for all ionic steps." - label: VaspData - "Label of all the data from the OSZICAR file." - is_elmin_converged: VaspData - "Is the electronic minimization step converged?" - - @dataclasses.dataclass class PairCorrelation: """The pair-correlation function calculated during a MD simulation. @@ -344,7 +344,7 @@ class PairCorrelation: @dataclasses.dataclass -class PartialCharge: +class PartialDensity: """Electronic partial charge and magnetization density on the fine Fourier grid Possibly not only split by spin, but also by band and kpoint.""" @@ -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 1e82e326..69858485 100644 --- a/src/py4vasp/_raw/definition.py +++ b/src/py4vasp/_raw/definition.py @@ -285,6 +285,14 @@ def selections(quantity): relaxed_ion=f"{group}/relaxed_ion_elastic_modulus", ) # +schema.add( + raw.ElectronicMinimization, + required=raw.Version(6, 5), + label="intermediate/ion_dynamics/oszicar_label", + convergence_data="intermediate/ion_dynamics/oszicar", + is_elmin_converged="/intermediate/ion_dynamics/electronic_step_converged", +) +# group = "results/linear_response" schema.add( raw.Fatband, @@ -378,14 +386,6 @@ def selections(quantity): orbital_moments="intermediate/ion_dynamics/magnetism/orbital_moments/values", ) # -schema.add( - raw.ElectronicMinimization, - required=raw.Version(6, 5), - label="intermediate/ion_dynamics/oszicar_label", - convergence_data="intermediate/ion_dynamics/oszicar", - is_elmin_converged="/intermediate/ion_dynamics/electronic_step_converged", -) -# group = "intermediate/pair_correlation" schema.add( raw.PairCorrelation, @@ -396,7 +396,7 @@ def selections(quantity): ) # schema.add( - raw.PartialCharge, + raw.PartialDensity, required=raw.Version(6, 5), structure=Link("structure", DEFAULT_SOURCE), partial_charge="results/partial_charges/parchg", @@ -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/convert.py b/src/py4vasp/_util/convert.py index f54bb95a..e1253cd0 100644 --- a/src/py4vasp/_util/convert.py +++ b/src/py4vasp/_util/convert.py @@ -46,11 +46,10 @@ def _to_snakecase(word: str) -> str: return word.lower() -# NOTE: to_camelcase is the function camelize from the inflection package +# NOTE: to_camelcase is based on the function camelize from the inflection package # (Copyright (C) 2012-2020 Janne Vanhala) def to_camelcase(string: str, uppercase_first_letter: bool = True) -> str: - """ - Convert strings to CamelCase. + """Convert strings to CamelCase. Examples:: @@ -70,7 +69,7 @@ def to_camelcase(string: str, uppercase_first_letter: bool = True) -> str: lowerCamelCase. Defaults to `True`. """ if uppercase_first_letter: - return re.sub(r"(?:^|_)(.)", lambda m: m.group(1).upper(), string) + return re.sub(r"(?:_|^)(.)", lambda m: m.group(1).upper(), string) else: return string[0].lower() + camelize(string)[1:] diff --git a/src/py4vasp/_util/parser.py b/src/py4vasp/_util/parser.py index 586e9ce9..76503ce5 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, Stoichiometry, Structure 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/src/py4vasp/exception.py b/src/py4vasp/exception.py index d0800864..437d5ae6 100644 --- a/src/py4vasp/exception.py +++ b/src/py4vasp/exception.py @@ -36,11 +36,6 @@ class OutdatedVaspVersion(Py4VaspError): used version of Vasp.""" -class MissingAttribute(Py4VaspError, AttributeError): - """Exception raised when py4vasp attribute of Calculation, Batch, ... is used - that does not exist""" - - class ModuleNotInstalled(Py4VaspError): """Exception raised when a functionality is used that relies on an optional dependency of py4vasp but that dependency is not installed.""" diff --git a/tests/analysis/test_mlff.py b/tests/analysis/test_mlff.py index 6439de0f..9b5d9aec 100644 --- a/tests/analysis/test_mlff.py +++ b/tests/analysis/test_mlff.py @@ -127,7 +127,7 @@ def mock_calculations_incorrect(raw_data): return _mock_calculations -@patch("py4vasp.calculation._base.Refinery.from_path", autospec=True) +@patch("py4vasp._calculation.base.Refinery.from_path", autospec=True) @patch("py4vasp.raw.access", autospec=True) def test_read_inputs_from_path(mock_access, mock_from_path): absolute_path_dft = Path(__file__) / "dft" @@ -151,7 +151,7 @@ def test_read_inputs_from_path(mock_access, mock_from_path): assert isinstance(error_analysis.dft.stresses, np.ndarray) -@patch("py4vasp.calculation._base.Refinery.from_path", autospec=True) +@patch("py4vasp._calculation.base.Refinery.from_path", autospec=True) @patch("py4vasp.raw.access", autospec=True) def test_read_inputs_from_files(mock_analysis, mock_from_path): absolute_files_dft = Path(__file__) / "dft*.h5" @@ -180,9 +180,9 @@ def test_read_from_data(mock_calculations): expected_forces = mock_calculations.forces.read() expected_stresses = mock_calculations.stresses.read() mlff_error_analysis = MLFFErrorAnalysis._from_data(mock_calculations) - output_energies = mlff_error_analysis._calculations.energies.read() - output_forces = mlff_error_analysis._calculations.forces.read() - output_stresses = mlff_error_analysis._calculations.stresses.read() + output_energies = mlff_error_analysis._batch.energies.read() + output_forces = mlff_error_analysis._batch.forces.read() + output_stresses = mlff_error_analysis._batch.stresses.read() assert output_energies == expected_energies assert output_forces == expected_forces assert output_stresses == expected_stresses diff --git a/tests/test_calculations.py b/tests/batch/test_batch.py similarity index 71% rename from tests/test_calculations.py rename to tests/batch/test_batch.py index 6bfd5af0..aac2a39b 100644 --- a/tests/test_calculations.py +++ b/tests/batch/test_batch.py @@ -6,54 +6,50 @@ import pytest -from py4vasp import Calculations +from py4vasp import Batch def test_error_when_using_constructor(): with pytest.raises(Exception): - Calculations() + Batch() def test_creation_from_paths(): # Test creation from absolute paths absolute_path_1 = Path(__file__) / "path_1" absolute_path_2 = Path(__file__) / "path_2" - calculations = Calculations.from_paths( - path_name_1=absolute_path_1, path_name_2=absolute_path_2 - ) - output_paths = calculations.paths() + batch = Batch.from_paths(path_name_1=absolute_path_1, path_name_2=absolute_path_2) + output_paths = batch.paths() assert output_paths["path_name_1"] == [absolute_path_1] assert output_paths["path_name_2"] == [absolute_path_2] - output_number_of_calculations = calculations.number_of_calculations() + output_number_of_calculations = batch.number_of_calculations() assert output_number_of_calculations["path_name_1"] == 1 assert output_number_of_calculations["path_name_2"] == 1 # Test creation from relative paths relative_path_1 = os.path.relpath(absolute_path_1, Path.cwd()) relative_path_2 = os.path.relpath(absolute_path_2, Path.cwd()) - calculations = Calculations.from_paths( - path_name_1=relative_path_1, path_name_2=relative_path_2 - ) - output_paths = calculations.paths() + batch = Batch.from_paths(path_name_1=relative_path_1, path_name_2=relative_path_2) + output_paths = batch.paths() assert output_paths["path_name_1"] == [absolute_path_1] assert output_paths["path_name_2"] == [absolute_path_2] - output_number_of_calculations = calculations.number_of_calculations() + output_number_of_calculations = batch.number_of_calculations() assert output_number_of_calculations["path_name_1"] == 1 assert output_number_of_calculations["path_name_2"] == 1 # Test creation with string paths - calculations = Calculations.from_paths( + batch = Batch.from_paths( path_name_1=absolute_path_1.as_posix(), path_name_2=absolute_path_2.as_posix() ) - output_paths = calculations.paths() + output_paths = batch.paths() assert output_paths["path_name_1"] == [absolute_path_1] assert output_paths["path_name_2"] == [absolute_path_2] - output_number_of_calculations = calculations.number_of_calculations() + output_number_of_calculations = batch.number_of_calculations() assert output_number_of_calculations["path_name_1"] == 1 assert output_number_of_calculations["path_name_2"] == 1 def test_creation_from_paths_with_incorrect_input(): with pytest.raises(Exception): - Calculations.from_paths(path_name_1=1, path_name_2=2) + Batch.from_paths(path_name_1=1, path_name_2=2) def test_creation_from_paths_with_wildcards(tmp_path): @@ -64,10 +60,10 @@ def test_creation_from_paths_with_wildcards(tmp_path): create_paths = lambda paths: [path.mkdir() for path in paths] create_paths(paths_1) create_paths(paths_2) - calculations = Calculations.from_paths( + batch = Batch.from_paths( path_name_1=tmp_path / "path1_*", path_name_2=tmp_path / "path2_*" ) - output_paths = calculations.paths() + output_paths = batch.paths() assert all( [ output_paths["path_name_1"][i] == absolute_paths_1[i] @@ -80,7 +76,7 @@ def test_creation_from_paths_with_wildcards(tmp_path): for i in range(len(absolute_paths_2)) ] ) - output_number_of_calculations = calculations.number_of_calculations() + output_number_of_calculations = batch.number_of_calculations() assert output_number_of_calculations["path_name_1"] == 2 assert output_number_of_calculations["path_name_2"] == 2 @@ -88,13 +84,11 @@ def test_creation_from_paths_with_wildcards(tmp_path): def test_creation_from_file(): absolute_path_1 = Path(__file__) / "example_1.h5" absolute_path_2 = Path(__file__) / "example_2.h5" - calculations = Calculations.from_files( - path_name_1=absolute_path_1, path_name_2=absolute_path_2 - ) - output_paths = calculations.paths() + batch = Batch.from_files(path_name_1=absolute_path_1, path_name_2=absolute_path_2) + output_paths = batch.paths() assert output_paths["path_name_1"] == [absolute_path_1.parent] assert output_paths["path_name_2"] == [absolute_path_2.parent] - output_number_of_calculations = calculations.number_of_calculations() + output_number_of_calculations = batch.number_of_calculations() assert output_number_of_calculations["path_name_1"] == 1 assert output_number_of_calculations["path_name_2"] == 1 @@ -107,11 +101,11 @@ def test_create_from_files_with_wildcards(tmp_path): create_files = lambda paths: [path.touch() for path in paths] create_files(paths_1) create_files(paths_2) - calculations = Calculations.from_files( + batch = Batch.from_files( file_1=tmp_path / "example1_*.h5", file_2=tmp_path / "example2_*.h5", ) - output_paths = calculations.paths() + output_paths = batch.paths() assert all( [ output_paths["file_1"][i] == absolute_paths_1[i].parent @@ -124,32 +118,32 @@ def test_create_from_files_with_wildcards(tmp_path): for i in range(len(absolute_paths_2)) ] ) - output_number_of_calculations = calculations.number_of_calculations() + output_number_of_calculations = batch.number_of_calculations() assert output_number_of_calculations["file_1"] == 2 assert output_number_of_calculations["file_2"] == 2 -@patch("py4vasp.calculation._base.Refinery.from_path", autospec=True) +@patch("py4vasp._calculation.base.Refinery.from_path", autospec=True) @patch("py4vasp.raw.access", autospec=True) def test_has_attributes(mock_access, mock_from_path): - calculations = Calculations.from_paths(path_name_1="path_1", path_name_2="path_2") - assert hasattr(calculations, "energies") - assert hasattr(calculations.energies, "read") - output_read = calculations.energies.read() + batch = Batch.from_paths(path_name_1="path_1", path_name_2="path_2") + assert hasattr(batch, "energies") + assert hasattr(batch.energies, "read") + output_read = batch.energies.read() assert isinstance(output_read, dict) assert output_read.keys() == {"path_name_1", "path_name_2"} assert isinstance(output_read["path_name_1"], list) assert isinstance(output_read["path_name_2"], list) - assert hasattr(calculations, "forces") - assert hasattr(calculations.forces, "read") - output_read = calculations.forces.read() + assert hasattr(batch, "forces") + assert hasattr(batch.forces, "read") + output_read = batch.forces.read() assert isinstance(output_read, dict) assert output_read.keys() == {"path_name_1", "path_name_2"} assert isinstance(output_read["path_name_1"], list) assert isinstance(output_read["path_name_2"], list) - assert hasattr(calculations, "stresses") - assert hasattr(calculations.stresses, "read") - output_read = calculations.stresses.read() + assert hasattr(batch, "stresses") + assert hasattr(batch.stresses, "read") + output_read = batch.stresses.read() assert isinstance(output_read, dict) assert output_read.keys() == {"path_name_1", "path_name_2"} assert isinstance(output_read["path_name_1"], list) diff --git a/tests/calculation/test_band.py b/tests/calculation/test_band.py index 490656bb..14ecfe9e 100644 --- a/tests/calculation/test_band.py +++ b/tests/calculation/test_band.py @@ -293,7 +293,7 @@ def test_plot_incorrect_width(with_projectors): with_projectors.plot("Sr", width="not a number") -@patch("py4vasp.calculation._band.Band.to_graph") +@patch("py4vasp._calculation.band.Band.to_graph") def test_to_plotly(mock_plot, single_band): fig = single_band.to_plotly("selection", width=0.2) mock_plot.assert_called_once_with("selection", width=0.2) @@ -309,7 +309,7 @@ def test_to_image(single_band): def check_to_image(single_band, filename_argument, expected_filename): - with patch("py4vasp.calculation._band.Band.to_plotly") as plot: + with patch("py4vasp._calculation.band.Band.to_plotly") as plot: single_band.to_image("args", filename=filename_argument, key="word") plot.assert_called_once_with("args", key="word") fig = plot.return_value diff --git a/tests/calculation/test_bandgap.py b/tests/calculation/test_bandgap.py index 91b27968..9ca30436 100644 --- a/tests/calculation/test_bandgap.py +++ b/tests/calculation/test_bandgap.py @@ -164,8 +164,8 @@ def test_plot_incorrect_selection(bandgap, selection): bandgap.plot(selection) -@patch("py4vasp.calculation._bandgap.Bandgap.to_graph") -def test_energy_to_plotly(mock_plot, bandgap): +@patch("py4vasp._calculation.bandgap.Bandgap.to_graph") +def test_bandgap_to_plotly(mock_plot, bandgap): fig = bandgap.to_plotly() mock_plot.assert_called_once_with() graph = mock_plot.return_value @@ -180,7 +180,7 @@ def test_to_image(bandgap): def check_to_image(bandgap, filename_argument, expected_filename): - with patch("py4vasp.calculation._bandgap.Bandgap.to_plotly") as plot: + with patch("py4vasp._calculation.bandgap.Bandgap.to_plotly") as plot: bandgap.to_image("args", filename=filename_argument, key="word") plot.assert_called_once_with("args", key="word") fig = plot.return_value diff --git a/tests/calculation/test_base.py b/tests/calculation/test_base.py index 543cf3af..66a6946e 100644 --- a/tests/calculation/test_base.py +++ b/tests/calculation/test_base.py @@ -11,8 +11,8 @@ import pytest from py4vasp import exception, raw +from py4vasp._calculation import base from py4vasp._util import select -from py4vasp.calculation import _base from .conftest import SELECTION @@ -39,40 +39,40 @@ def mock_behavior(quantity, *, selection=None, path=None, file=None): yield access -class Example(_base.Refinery): +class Example(base.Refinery): def __post_init__(self): self.post_init_called = True - @_base.data_access + @base.data_access def to_dict(self): "to_dict documentation." return self._raw_data.content - @_base.data_access + @base.data_access def wrapper(self): return self.read() - @_base.data_access + @base.data_access def with_arguments(self, mandatory, optional=None): return mandatory, optional - @_base.data_access + @base.data_access def with_variadic_arguments(self, *args, **kwargs): return args, kwargs - @_base.data_access + @base.data_access def with_selection_argument(self, selection=DEFAULT_SELECTION): return self._raw_data.selection, selection - @_base.data_access + @base.data_access def selection_without_default(self, selection): return selection - @_base.data_access + @base.data_access def selection_from_property(self): return self._selection - @_base.data_access + @base.data_access def __str__(self): return self._raw_data.content @@ -255,8 +255,8 @@ def check_mock(example, mock, *args, **kwargs): mock.reset_mock() -class CamelCase(_base.Refinery): - @_base.data_access +class CamelCase(base.Refinery): + @base.data_access def to_dict(self): return "convert CamelCase to snake_case" diff --git a/tests/calculation/test_class.py b/tests/calculation/test_class.py index 441ffbce..aa8935ac 100644 --- a/tests/calculation/test_class.py +++ b/tests/calculation/test_class.py @@ -6,10 +6,10 @@ import pytest -from py4vasp import Calculation, calculation, control, exception +from py4vasp import Calculation, _calculation, control, exception -@patch("py4vasp.calculation._base.Refinery.from_path", autospec=True) +@patch("py4vasp._calculation.base.Refinery.from_path", autospec=True) @patch("py4vasp.raw.access", autospec=True) def test_creation_from_path(mock_access, mock_from_path): # note: in pytest __file__ defaults to absolute path @@ -25,7 +25,7 @@ def test_creation_from_path(mock_access, mock_from_path): mock_from_path.assert_called() -@patch("py4vasp.calculation._base.Refinery.from_file", autospec=True) +@patch("py4vasp._calculation.base.Refinery.from_file", autospec=True) @patch("py4vasp.raw.access", autospec=True) def test_creation_from_file(mock_access, mock_from_file): # note: in pytest __file__ defaults to absolute path @@ -45,7 +45,7 @@ def test_creation_from_file(mock_access, mock_from_file): @patch("py4vasp.raw.access", autospec=True) def test_all_attributes(mock_access): calc = Calculation.from_path("test_path") - for name in calculation.__all__: + for name in _calculation.QUANTITIES: # + _calculation.INPUT_FILES: assert hasattr(calc, name) mock_access.assert_not_called() mock_access.return_value.__enter__.assert_not_called() diff --git a/tests/calculation/test_contcar.py b/tests/calculation/test_contcar.py index d3006112..66962d97 100644 --- a/tests/calculation/test_contcar.py +++ b/tests/calculation/test_contcar.py @@ -61,7 +61,7 @@ def CONTCAR(raw_data, request): selection = request.param raw_contcar = raw_data.CONTCAR(selection) - contcar = calculation.CONTCAR.from_data(raw_contcar) + contcar = calculation._CONTCAR.from_data(raw_contcar) contcar.ref = types.SimpleNamespace() structure = calculation.structure.from_data(raw_data.structure(selection))[-1] contcar.ref.structure = structure @@ -116,4 +116,4 @@ def test_print(CONTCAR, format_): def test_factory_methods(raw_data, check_factory_methods): raw_contcar = raw_data.CONTCAR("Sr2TiO4") - check_factory_methods(calculation.CONTCAR, raw_contcar) + check_factory_methods(calculation._CONTCAR, raw_contcar) diff --git a/tests/calculation/test_module.py b/tests/calculation/test_default_calculation.py similarity index 63% rename from tests/calculation/test_module.py rename to tests/calculation/test_default_calculation.py index 52065114..07435d3d 100644 --- a/tests/calculation/test_module.py +++ b/tests/calculation/test_default_calculation.py @@ -19,6 +19,11 @@ def attribute_included(attr): return True -def test_nonexisting_attribute(): - with pytest.raises(exception.MissingAttribute): - calculation.does_not_exist +@pytest.mark.skip("Input files are not included in current release.") +def test_assigning_to_input_file(tmp_path, monkeypatch): + monkeypatch.chdir(tmp_path) + expected = "SYSTEM = demo INCAR file" + calculation.INCAR = expected + with open("INCAR", "r") as file: + actual = file.read() + assert actual == expected diff --git a/tests/calculation/test_dielectric_function.py b/tests/calculation/test_dielectric_function.py index 69cd110e..d9dd4a7d 100644 --- a/tests/calculation/test_dielectric_function.py +++ b/tests/calculation/test_dielectric_function.py @@ -299,7 +299,7 @@ def check_figure_contains_plots(fig, references, Assert): assert data.name == ref.name -@patch("py4vasp.calculation._dielectric_function.DielectricFunction.to_graph") +@patch("py4vasp._calculation.dielectric_function.DielectricFunction.to_graph") def test_electronic_to_plotly(mock_plot, electronic): fig = electronic.to_plotly("selection") mock_plot.assert_called_once_with("selection") @@ -322,7 +322,7 @@ def test_ionic_to_image(ionic): def check_to_image(dielectric_function, filename_argument, expected_filename): plot_function = ( - "py4vasp.calculation._dielectric_function.DielectricFunction.to_plotly" + "py4vasp._calculation.dielectric_function.DielectricFunction.to_plotly" ) with patch(plot_function) as plot: dielectric_function.to_image("args", filename=filename_argument, key="word") diff --git a/tests/calculation/test_dispersion.py b/tests/calculation/test_dispersion.py index 1f22ad85..d4ca7300 100644 --- a/tests/calculation/test_dispersion.py +++ b/tests/calculation/test_dispersion.py @@ -11,7 +11,7 @@ @pytest.fixture(params=["single_band", "spin_polarized", "line", "phonon"]) def dispersion(raw_data, request): raw_dispersion = raw_data.dispersion(request.param) - dispersion = calculation.dispersion.from_data(raw_dispersion) + dispersion = calculation._dispersion.from_data(raw_dispersion) dispersion.ref = types.SimpleNamespace() dispersion.ref.kpoints = calculation.kpoint.from_data(raw_dispersion.kpoints) dispersion.ref.eigenvalues = raw_dispersion.eigenvalues @@ -98,4 +98,4 @@ def test_print(dispersion, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.dispersion("single_band") - check_factory_methods(calculation.dispersion, data) + check_factory_methods(calculation._dispersion, data) diff --git a/tests/calculation/test_dos.py b/tests/calculation/test_dos.py index b6eb69a9..9f49fb04 100644 --- a/tests/calculation/test_dos.py +++ b/tests/calculation/test_dos.py @@ -206,7 +206,7 @@ def test_plot_combine_projectors(Fe3O4_projectors, Assert): Assert.allclose(data[names.index("Fe_down - p_down")].y, -subtraction_down) -@patch("py4vasp.calculation._dos.Dos.to_graph") +@patch("py4vasp._calculation.dos.Dos.to_graph") def test_Sr2TiO4_to_plotly(mock_plot, Sr2TiO4): fig = Sr2TiO4.to_plotly("selection") mock_plot.assert_called_once_with("selection") @@ -222,7 +222,7 @@ def test_Sr2TiO4_to_image(Sr2TiO4): def check_to_image(Sr2TiO4, filename_argument, expected_filename): - with patch("py4vasp.calculation._dos.Dos.to_plotly") as plot: + with patch("py4vasp._calculation.dos.Dos.to_plotly") as plot: Sr2TiO4.to_image("args", filename=filename_argument, key="word") plot.assert_called_once_with("args", key="word") fig = plot.return_value diff --git a/tests/calculation/test_electronic_minimization.py b/tests/calculation/test_electronic_minimization.py index 88350470..60579698 100644 --- a/tests/calculation/test_electronic_minimization.py +++ b/tests/calculation/test_electronic_minimization.py @@ -10,30 +10,32 @@ @pytest.fixture -def ElectronicMinimization(raw_data): - raw_oszicar = raw_data.electronic_minimization() - oszicar = calculation.electronic_minimization.from_data(raw_oszicar) - oszicar.ref = types.SimpleNamespace() - convergence_data = raw_oszicar.convergence_data - oszicar.ref.N = np.int64(convergence_data[:, 0]) - oszicar.ref.E = convergence_data[:, 1] - oszicar.ref.dE = convergence_data[:, 2] - oszicar.ref.deps = convergence_data[:, 3] - oszicar.ref.ncg = convergence_data[:, 4] - oszicar.ref.rms = convergence_data[:, 5] - oszicar.ref.rmsc = convergence_data[:, 6] - oszicar.ref.is_elmin_converged = [raw_oszicar.is_elmin_converged == [0.0]] +def electronic_minimization(raw_data): + raw_electronic_minimization = raw_data.electronic_minimization() + constructor = calculation.electronic_minimization.from_data + electronic_minimization = constructor(raw_electronic_minimization) + electronic_minimization.ref = types.SimpleNamespace() + convergence_data = raw_electronic_minimization.convergence_data + electronic_minimization.ref.N = np.int64(convergence_data[:, 0]) + electronic_minimization.ref.E = convergence_data[:, 1] + electronic_minimization.ref.dE = convergence_data[:, 2] + electronic_minimization.ref.deps = convergence_data[:, 3] + electronic_minimization.ref.ncg = convergence_data[:, 4] + electronic_minimization.ref.rms = convergence_data[:, 5] + electronic_minimization.ref.rmsc = convergence_data[:, 6] + is_elmin_converged = [raw_electronic_minimization.is_elmin_converged == [0.0]] + electronic_minimization.ref.is_elmin_converged = is_elmin_converged string_rep = "N\t\tE\t\tdE\t\tdeps\t\tncg\trms\t\trms(c)\n" format_rep = "{0:g}\t{1:0.12E}\t{2:0.6E}\t{3:0.6E}\t{4:g}\t{5:0.3E}\t{6:0.3E}\n" for idx in range(len(convergence_data)): string_rep += format_rep.format(*convergence_data[idx]) - oszicar.ref.string_rep = str(string_rep) - return oszicar + electronic_minimization.ref.string_rep = str(string_rep) + return electronic_minimization -def test_read(ElectronicMinimization, Assert): - actual = ElectronicMinimization.read() - expected = ElectronicMinimization.ref +def test_read(electronic_minimization, Assert): + actual = electronic_minimization.read() + expected = electronic_minimization.ref Assert.allclose(actual["N"], expected.N) Assert.allclose(actual["E"], expected.E) Assert.allclose(actual["dE"], expected.dE) @@ -46,22 +48,21 @@ def test_read(ElectronicMinimization, Assert): @pytest.mark.parametrize( "quantity_name", ["N", "E", "dE", "deps", "ncg", "rms", "rms(c)"] ) -def test_read_selection(quantity_name, ElectronicMinimization, Assert): - actual = ElectronicMinimization.read(quantity_name) - expected = getattr( - ElectronicMinimization.ref, quantity_name.replace("(", "").replace(")", "") - ) +def test_read_selection(quantity_name, electronic_minimization, Assert): + actual = electronic_minimization.read(quantity_name) + name_without_parenthesis = quantity_name.replace("(", "").replace(")", "") + expected = getattr(electronic_minimization.ref, name_without_parenthesis) Assert.allclose(actual[quantity_name], expected) -def test_read_incorrect_selection(ElectronicMinimization): +def test_read_incorrect_selection(electronic_minimization): with pytest.raises(exception.RefinementError): - ElectronicMinimization.read("forces") + electronic_minimization.read("forces") -def test_slice(ElectronicMinimization, Assert): - actual = ElectronicMinimization[0:1].read() - expected = ElectronicMinimization.ref +def test_slice(electronic_minimization, Assert): + actual = electronic_minimization[0:1].read() + expected = electronic_minimization.ref Assert.allclose(actual["N"], expected.N) Assert.allclose(actual["E"], expected.E) Assert.allclose(actual["dE"], expected.dE) @@ -71,21 +72,21 @@ def test_slice(ElectronicMinimization, Assert): Assert.allclose(actual["rms(c)"], expected.rmsc) -def test_plot(ElectronicMinimization, Assert): - graph = ElectronicMinimization.plot() +def test_plot(electronic_minimization, Assert): + graph = electronic_minimization.plot() assert graph.xlabel == "Iteration number" assert graph.ylabel == "E" assert len(graph.series) == 1 - Assert.allclose(graph.series[0].x, ElectronicMinimization.ref.N) - Assert.allclose(graph.series[0].y, ElectronicMinimization.ref.E) + Assert.allclose(graph.series[0].x, electronic_minimization.ref.N) + Assert.allclose(graph.series[0].y, electronic_minimization.ref.E) -def test_print(ElectronicMinimization, format_): - actual, _ = format_(ElectronicMinimization) - assert actual["text/plain"] == ElectronicMinimization.ref.string_rep +def test_print(electronic_minimization, format_): + actual, _ = format_(electronic_minimization) + assert actual["text/plain"] == electronic_minimization.ref.string_rep -def test_is_converged(ElectronicMinimization): - actual = ElectronicMinimization.is_converged() - expected = ElectronicMinimization.ref.is_elmin_converged +def test_is_converged(electronic_minimization): + actual = electronic_minimization.is_converged() + expected = electronic_minimization.ref.is_elmin_converged assert actual == expected diff --git a/tests/calculation/test_energy.py b/tests/calculation/test_energy.py index b301ec6f..2df79aa3 100644 --- a/tests/calculation/test_energy.py +++ b/tests/calculation/test_energy.py @@ -96,7 +96,7 @@ def test_incorrect_label(MD_energy): MD_energy.plot(number_instead_of_string) -@patch("py4vasp.calculation._energy.Energy.to_graph") +@patch("py4vasp._calculation.energy.Energy.to_graph") def test_energy_to_plotly(mock_plot, MD_energy): fig = MD_energy.to_plotly("selection") mock_plot.assert_called_once_with("selection") @@ -112,7 +112,7 @@ def test_to_image(MD_energy): def check_to_image(MD_energy, filename_argument, expected_filename): - with patch("py4vasp.calculation._energy.Energy.to_plotly") as plot: + with patch("py4vasp._calculation.energy.Energy.to_plotly") as plot: MD_energy.to_image("args", filename=filename_argument, key="word") plot.assert_called_once_with("args", key="word") fig = plot.return_value diff --git a/tests/calculation/test_fatband.py b/tests/calculation/test_fatband.py index f2a07936..d0f1891c 100644 --- a/tests/calculation/test_fatband.py +++ b/tests/calculation/test_fatband.py @@ -13,7 +13,7 @@ def fatband(raw_data): raw_fatband = raw_data.fatband("default") fatband = calculation.fatband.from_data(raw_fatband) fatband.ref = types.SimpleNamespace() - fatband.ref.dispersion = calculation.dispersion.from_data(raw_fatband.dispersion) + fatband.ref.dispersion = calculation._dispersion.from_data(raw_fatband.dispersion) fatbands = raw_fatband.fatbands fatband.ref.fatbands = fatbands[:, :, 0] + fatbands[:, :, 1] * 1j fatband.ref.fermi_energy = raw_fatband.fermi_energy diff --git a/tests/calculation/test_magnetism.py b/tests/calculation/test_magnetism.py index 406deed6..a43b954f 100644 --- a/tests/calculation/test_magnetism.py +++ b/tests/calculation/test_magnetism.py @@ -63,7 +63,8 @@ def __getitem__(self, step): reference.moments = np.moveaxis(raw_magnetism.spin_moments[:, 1:4], 1, 3) else: spin_moments = np.moveaxis(raw_magnetism.spin_moments[:, 1:4], 1, 3) - orbital_moments = np.moveaxis(raw_magnetism.orbital_moments[:, 1:4], 1, 3) + orbital_moments = np.zeros_like(spin_moments).astype(np.float64) + orbital_moments[:, :, 1:] += np.moveaxis(raw_magnetism.orbital_moments, 1, 3) reference.moments = spin_moments + orbital_moments reference.spin_moments = spin_moments reference.orbital_moments = orbital_moments diff --git a/tests/calculation/test_pair_correlation.py b/tests/calculation/test_pair_correlation.py index c3063919..17c3be39 100644 --- a/tests/calculation/test_pair_correlation.py +++ b/tests/calculation/test_pair_correlation.py @@ -71,7 +71,7 @@ def test_plot_nonexisting_label(pair_correlation): pair_correlation.plot("label does exist") -@patch("py4vasp.calculation._pair_correlation.PairCorrelation.to_graph") +@patch("py4vasp._calculation.pair_correlation.PairCorrelation.to_graph") def test_pair_correlation_to_plotly(mock_plot, pair_correlation): fig = pair_correlation.to_plotly("selection") mock_plot.assert_called_once_with("selection") @@ -87,7 +87,7 @@ def test_to_image(pair_correlation): def check_to_image(pair_correlation, filename_argument, expected_filename): - function = "py4vasp.calculation._pair_correlation.PairCorrelation.to_plotly" + function = "py4vasp._calculation.pair_correlation.PairCorrelation.to_plotly" with patch(function) as plot: pair_correlation.to_image("args", filename=filename_argument, key="word") plot.assert_called_once_with("args", key="word") diff --git a/tests/calculation/test_partial_charge.py b/tests/calculation/test_partial_density.py similarity index 58% rename from tests/calculation/test_partial_charge.py rename to tests/calculation/test_partial_density.py index 53009a3f..1dfce5b0 100644 --- a/tests/calculation/test_partial_charge.py +++ b/tests/calculation/test_partial_density.py @@ -7,8 +7,8 @@ import pytest from py4vasp import calculation +from py4vasp._calculation.partial_density import STM_settings from py4vasp._util.slicing import plane -from py4vasp.calculation._partial_charge import STM_settings from py4vasp.exception import IncorrectUsage, NoData, NotImplemented @@ -35,48 +35,48 @@ "split_bands and split_kpoints and spin_polarized Sr2TiO4", ] ) -def PartialCharge(raw_data, request): - return make_reference_partial_charge(raw_data, request.param) +def PartialDensity(raw_data, request): + return make_reference_partial_density(raw_data, request.param) @pytest.fixture -def NonSplitPartialCharge(raw_data): - return make_reference_partial_charge(raw_data, "no splitting no spin") +def NonSplitPartialDensity(raw_data): + return make_reference_partial_density(raw_data, "no splitting no spin") @pytest.fixture -def PolarizedNonSplitPartialCharge(raw_data): - return make_reference_partial_charge(raw_data, "spin_polarized") +def PolarizedNonSplitPartialDensity(raw_data): + return make_reference_partial_density(raw_data, "spin_polarized") @pytest.fixture -def PolarizedNonSplitPartialChargeCa3AsBr3(raw_data): - return make_reference_partial_charge(raw_data, "spin_polarized Ca3AsBr3") +def PolarizedNonSplitPartialDensityCa3AsBr3(raw_data): + return make_reference_partial_density(raw_data, "spin_polarized Ca3AsBr3") @pytest.fixture -def NonSplitPartialChargeCaAs3_110(raw_data): - return make_reference_partial_charge(raw_data, "CaAs3_110") +def NonSplitPartialDensityCaAs3_110(raw_data): + return make_reference_partial_density(raw_data, "CaAs3_110") @pytest.fixture -def NonSplitPartialChargeNi_100(raw_data): - return make_reference_partial_charge(raw_data, "Ni100") +def NonSplitPartialDensityNi_100(raw_data): + return make_reference_partial_density(raw_data, "Ni100") @pytest.fixture -def PolarizedNonSplitPartialChargeSr2TiO4(raw_data): - return make_reference_partial_charge(raw_data, "spin_polarized Sr2TiO4") +def PolarizedNonSplitPartialDensitySr2TiO4(raw_data): + return make_reference_partial_density(raw_data, "spin_polarized Sr2TiO4") @pytest.fixture -def NonPolarizedBandSplitPartialCharge(raw_data): - return make_reference_partial_charge(raw_data, "split_bands") +def NonPolarizedBandSplitPartialDensity(raw_data): + return make_reference_partial_density(raw_data, "split_bands") @pytest.fixture -def PolarizedAllSplitPartialCharge(raw_data): - return make_reference_partial_charge( +def PolarizedAllSplitPartialDensity(raw_data): + return make_reference_partial_density( raw_data, "split_bands and split_kpoints and spin_polarized" ) @@ -86,121 +86,123 @@ def spin(request): return request.param -def make_reference_partial_charge(raw_data, selection): - raw_partial_charge = raw_data.partial_charge(selection=selection) - parchg = calculation.partial_charge.from_data(raw_partial_charge) +def make_reference_partial_density(raw_data, selection): + raw_partial_density = raw_data.partial_density(selection=selection) + parchg = calculation.partial_density.from_data(raw_partial_density) parchg.ref = types.SimpleNamespace() - parchg.ref.structure = calculation.structure.from_data(raw_partial_charge.structure) + parchg.ref.structure = calculation.structure.from_data( + raw_partial_density.structure + ) parchg.ref.plane_vectors = plane( cell=parchg.ref.structure.lattice_vectors(), cut="c", normal="z", ) - parchg.ref.partial_charge = raw_partial_charge.partial_charge - parchg.ref.bands = raw_partial_charge.bands - parchg.ref.kpoints = raw_partial_charge.kpoints - parchg.ref.grid = raw_partial_charge.grid + parchg.ref.partial_density = raw_partial_density.partial_charge + parchg.ref.bands = raw_partial_density.bands + parchg.ref.kpoints = raw_partial_density.kpoints + parchg.ref.grid = raw_partial_density.grid return parchg -def test_read(PartialCharge, Assert, not_core): - actual = PartialCharge.read() - expected = PartialCharge.ref +def test_read(PartialDensity, Assert, not_core): + actual = PartialDensity.read() + expected = PartialDensity.ref Assert.allclose(actual["bands"], expected.bands) Assert.allclose(actual["kpoints"], expected.kpoints) Assert.allclose(actual["grid"], expected.grid) - expected_charge = np.squeeze(np.asarray(expected.partial_charge).T) - Assert.allclose(actual["partial_charge"], expected_charge) + expected_density = np.squeeze(np.asarray(expected.partial_density).T) + Assert.allclose(actual["partial_density"], expected_density) Assert.same_structure(actual["structure"], expected.structure.read()) -def test_topology(PartialCharge, not_core): - actual = PartialCharge._topology() - expected = str(PartialCharge.ref.structure._topology()) +def test_stoichiometry(PartialDensity, not_core): + actual = PartialDensity._stoichiometry() + expected = str(PartialDensity.ref.structure._stoichiometry()) assert actual == expected -def test_bands(PartialCharge, Assert, not_core): - actual = PartialCharge.bands() - expected = PartialCharge.ref.bands +def test_bands(PartialDensity, Assert, not_core): + actual = PartialDensity.bands() + expected = PartialDensity.ref.bands Assert.allclose(actual, expected) -def test_kpoints(PartialCharge, Assert, not_core): - actual = PartialCharge.kpoints() - expected = PartialCharge.ref.kpoints +def test_kpoints(PartialDensity, Assert, not_core): + actual = PartialDensity.kpoints() + expected = PartialDensity.ref.kpoints Assert.allclose(actual, expected) -def test_grid(PartialCharge, Assert, not_core): - actual = PartialCharge.grid() - expected = PartialCharge.ref.grid +def test_grid(PartialDensity, Assert, not_core): + actual = PartialDensity.grid() + expected = PartialDensity.ref.grid Assert.allclose(actual, expected) -def test_non_split_to_numpy(PolarizedNonSplitPartialCharge, Assert, not_core): - actual = PolarizedNonSplitPartialCharge.to_numpy("total") - expected = PolarizedNonSplitPartialCharge.ref.partial_charge +def test_non_split_to_numpy(PolarizedNonSplitPartialDensity, Assert, not_core): + actual = PolarizedNonSplitPartialDensity.to_numpy("total") + expected = PolarizedNonSplitPartialDensity.ref.partial_density Assert.allclose(actual, expected[0, 0, 0].T) - actual = PolarizedNonSplitPartialCharge.to_numpy("up") + actual = PolarizedNonSplitPartialDensity.to_numpy("up") Assert.allclose(actual, 0.5 * (expected[0, 0, 0].T + expected[0, 0, 1].T)) - actual = PolarizedNonSplitPartialCharge.to_numpy("down") + actual = PolarizedNonSplitPartialDensity.to_numpy("down") Assert.allclose(actual, 0.5 * (expected[0, 0, 0].T - expected[0, 0, 1].T)) -def test_split_to_numpy(PolarizedAllSplitPartialCharge, Assert, not_core): - bands = PolarizedAllSplitPartialCharge.ref.bands - kpoints = PolarizedAllSplitPartialCharge.ref.kpoints +def test_split_to_numpy(PolarizedAllSplitPartialDensity, Assert, not_core): + bands = PolarizedAllSplitPartialDensity.ref.bands + kpoints = PolarizedAllSplitPartialDensity.ref.kpoints for band_index, band in enumerate(bands): for kpoint_index, kpoint in enumerate(kpoints): - actual = PolarizedAllSplitPartialCharge.to_numpy( + actual = PolarizedAllSplitPartialDensity.to_numpy( band=band, kpoint=kpoint, selection="total" ) - expected = PolarizedAllSplitPartialCharge.ref.partial_charge + expected = PolarizedAllSplitPartialDensity.ref.partial_density Assert.allclose(actual, np.asarray(expected)[kpoint_index, band_index, 0].T) msg = f"Band {max(bands) + 1} not found in the bands array." with pytest.raises(NoData) as excinfo: - PolarizedAllSplitPartialCharge.to_numpy( + PolarizedAllSplitPartialDensity.to_numpy( band=max(bands) + 1, kpoint=max(kpoints), selection="up" ) assert msg in str(excinfo.value) msg = f"K-point {min(kpoints) - 1} not found in the kpoints array." with pytest.raises(NoData) as excinfo: - PolarizedAllSplitPartialCharge.to_numpy( + PolarizedAllSplitPartialDensity.to_numpy( band=min(bands), kpoint=min(kpoints) - 1, selection="down" ) assert msg in str(excinfo.value) -def test_non_polarized_to_numpy(NonSplitPartialCharge, spin, Assert, not_core): - actual = NonSplitPartialCharge.to_numpy(selection=spin) - expected = NonSplitPartialCharge.ref.partial_charge +def test_non_polarized_to_numpy(NonSplitPartialDensity, spin, Assert, not_core): + actual = NonSplitPartialDensity.to_numpy(selection=spin) + expected = NonSplitPartialDensity.ref.partial_density Assert.allclose(actual, np.asarray(expected).T[:, :, :, 0, 0, 0]) def test_split_bands_to_numpy( - NonPolarizedBandSplitPartialCharge, spin, Assert, not_core + NonPolarizedBandSplitPartialDensity, spin, Assert, not_core ): - bands = NonPolarizedBandSplitPartialCharge.ref.bands + bands = NonPolarizedBandSplitPartialDensity.ref.bands for band_index, band in enumerate(bands): - actual = NonPolarizedBandSplitPartialCharge.to_numpy(spin, band=band) - expected = NonPolarizedBandSplitPartialCharge.ref.partial_charge + actual = NonPolarizedBandSplitPartialDensity.to_numpy(spin, band=band) + expected = NonPolarizedBandSplitPartialDensity.ref.partial_density Assert.allclose(actual, np.asarray(expected).T[:, :, :, 0, band_index, 0]) -def test_to_stm_split(PolarizedAllSplitPartialCharge, not_core): +def test_to_stm_split(PolarizedAllSplitPartialDensity, not_core): msg = "set LSEPK and LSEPB to .FALSE. in the INCAR file." with pytest.raises(NotImplemented) as excinfo: - PolarizedAllSplitPartialCharge.to_stm(selection="constant_current") + PolarizedAllSplitPartialDensity.to_stm(selection="constant_current") assert msg in str(excinfo.value) -def test_to_stm_nonsplit_tip_to_high(NonSplitPartialCharge, not_core): - actual = NonSplitPartialCharge +def test_to_stm_nonsplit_tip_to_high(NonSplitPartialDensity, not_core): + actual = NonSplitPartialDensity tip_height = 8.4 error = f"""The tip position at {tip_height:.2f} is above half of the estimated vacuum thickness {actual._estimate_vacuum():.2f} Angstrom. @@ -210,45 +212,45 @@ def test_to_stm_nonsplit_tip_to_high(NonSplitPartialCharge, not_core): def test_to_stm_nonsplit_not_orthogonal_no_vacuum( - PolarizedNonSplitPartialChargeSr2TiO4, + PolarizedNonSplitPartialDensitySr2TiO4, not_core, ): msg = "The vacuum region in your cell is too small for STM simulations." with pytest.raises(IncorrectUsage) as excinfo: - PolarizedNonSplitPartialChargeSr2TiO4.to_stm() + PolarizedNonSplitPartialDensitySr2TiO4.to_stm() assert msg in str(excinfo.value) -def test_to_stm_wrong_spin_nonsplit(PolarizedNonSplitPartialCharge, not_core): +def test_to_stm_wrong_spin_nonsplit(PolarizedNonSplitPartialDensity, not_core): msg = "'up', 'down', or 'total'" with pytest.raises(IncorrectUsage) as excinfo: - PolarizedNonSplitPartialCharge.to_stm(selection="all") + PolarizedNonSplitPartialDensity.to_stm(selection="all") assert msg in str(excinfo.value) -def test_to_stm_wrong_mode(PolarizedNonSplitPartialCharge, not_core): +def test_to_stm_wrong_mode(PolarizedNonSplitPartialDensity, not_core): with pytest.raises(IncorrectUsage) as excinfo: - PolarizedNonSplitPartialCharge.to_stm(selection="stm") + PolarizedNonSplitPartialDensity.to_stm(selection="stm") assert "STM mode" in str(excinfo.value) -def test_wrong_vacuum_direction(NonSplitPartialChargeNi_100, not_core): +def test_wrong_vacuum_direction(NonSplitPartialDensityNi_100, not_core): msg = """The vacuum region in your cell is not located along the third lattice vector.""" with pytest.raises(NotImplemented) as excinfo: - NonSplitPartialChargeNi_100.to_stm() + NonSplitPartialDensityNi_100.to_stm() assert msg in str(excinfo.value) @pytest.mark.parametrize("alias", ("constant_height", "ch", "height")) def test_to_stm_nonsplit_constant_height( - PolarizedNonSplitPartialCharge, alias, spin, Assert, not_core + PolarizedNonSplitPartialDensity, alias, spin, Assert, not_core ): supercell = 3 - actual = PolarizedNonSplitPartialCharge.to_stm( + actual = PolarizedNonSplitPartialDensity.to_stm( selection=f"{alias}({spin})", tip_height=2.0, supercell=supercell ) - expected = PolarizedNonSplitPartialCharge.ref + expected = PolarizedNonSplitPartialDensity.ref assert type(actual.series.data) == np.ndarray assert actual.series.data.shape == (expected.grid[0], expected.grid[1]) Assert.allclose(actual.series.lattice.vectors, expected.plane_vectors.vectors) @@ -265,16 +267,16 @@ def test_to_stm_nonsplit_constant_height( @pytest.mark.parametrize("alias", ("constant_current", "cc", "current")) def test_to_stm_nonsplit_constant_current( - PolarizedNonSplitPartialCharge, alias, spin, Assert, not_core + PolarizedNonSplitPartialDensity, alias, spin, Assert, not_core ): current = 5 supercell = np.asarray([2, 4]) - actual = PolarizedNonSplitPartialCharge.to_stm( + actual = PolarizedNonSplitPartialDensity.to_stm( selection=f"{spin}({alias})", current=current, supercell=supercell, ) - expected = PolarizedNonSplitPartialCharge.ref + expected = PolarizedNonSplitPartialDensity.ref assert type(actual.series.data) == np.ndarray assert actual.series.data.shape == (expected.grid[0], expected.grid[1]) Assert.allclose(actual.series.lattice.vectors, expected.plane_vectors.vectors) @@ -291,16 +293,16 @@ def test_to_stm_nonsplit_constant_current( @pytest.mark.parametrize("alias", ("constant_current", "cc", "current")) def test_to_stm_nonsplit_constant_current_non_ortho( - NonSplitPartialChargeCaAs3_110, alias, spin, Assert, not_core + NonSplitPartialDensityCaAs3_110, alias, spin, Assert, not_core ): current = 5 supercell = np.asarray([2, 4]) - actual = NonSplitPartialChargeCaAs3_110.to_stm( + actual = NonSplitPartialDensityCaAs3_110.to_stm( selection=f"{spin}({alias})", current=current, supercell=supercell, ) - expected = NonSplitPartialChargeCaAs3_110.ref + expected = NonSplitPartialDensityCaAs3_110.ref assert type(actual.series.data) == np.ndarray assert actual.series.data.shape == (expected.grid[0], expected.grid[1]) Assert.allclose(actual.series.lattice.vectors, expected.plane_vectors.vectors) @@ -315,8 +317,8 @@ def test_to_stm_nonsplit_constant_current_non_ortho( assert f"{current:.2f}" in actual.title -def test_stm_settings(PolarizedNonSplitPartialCharge, not_core): - actual = dataclasses.asdict(PolarizedNonSplitPartialCharge.stm_settings) +def test_stm_default_settings(PolarizedNonSplitPartialDensity, not_core): + actual = dataclasses.asdict(PolarizedNonSplitPartialDensity.stm_settings) defaults = { "sigma_xy": 4.0, "sigma_z": 4.0, @@ -332,44 +334,44 @@ def test_stm_settings(PolarizedNonSplitPartialCharge, not_core): enhancement_factor=500, interpolation_factor=5, ) - graph = PolarizedNonSplitPartialCharge.to_stm(stm_settings=modified) + graph = PolarizedNonSplitPartialDensity.to_stm(stm_settings=modified) assert graph.series.settings == modified -def test_smoothening_change(PolarizedNonSplitPartialCharge, not_core): +def test_smoothening_change(PolarizedNonSplitPartialDensity, not_core): mod_settings = STM_settings(sigma_xy=2.0, sigma_z=2.0, truncate=1.0) - data = PolarizedNonSplitPartialCharge.to_numpy("total", band=0, kpoint=0) - default_smoothed_density = PolarizedNonSplitPartialCharge._smooth_stm_data( + data = PolarizedNonSplitPartialDensity.to_numpy("total", band=0, kpoint=0) + default_smoothed_density = PolarizedNonSplitPartialDensity._smooth_stm_data( data=data, stm_settings=STM_settings() ) - new_smoothed_density = PolarizedNonSplitPartialCharge._smooth_stm_data( + new_smoothed_density = PolarizedNonSplitPartialDensity._smooth_stm_data( data=data, stm_settings=mod_settings ) assert not np.allclose(default_smoothed_density, new_smoothed_density) -def test_enhancement_setting_change(PolarizedNonSplitPartialCharge, Assert, not_core): +def test_enhancement_setting_change(PolarizedNonSplitPartialDensity, Assert, not_core): enhance_settings = STM_settings( enhancement_factor=STM_settings().enhancement_factor / 2.0 ) - graph_def = PolarizedNonSplitPartialCharge.to_stm("constant_height") - graph_less_enhanced = PolarizedNonSplitPartialCharge.to_stm( + graph_def = PolarizedNonSplitPartialDensity.to_stm("constant_height") + graph_less_enhanced = PolarizedNonSplitPartialDensity.to_stm( "constant_height", stm_settings=enhance_settings ) Assert.allclose(graph_def.series.data, graph_less_enhanced.series.data * 2) -def test_interpolation_setting_change(PolarizedNonSplitPartialCharge, not_core): +def test_interpolation_setting_change(PolarizedNonSplitPartialDensity, not_core): interp_settings = STM_settings( interpolation_factor=STM_settings().interpolation_factor / 4.0 ) - graph_def = PolarizedNonSplitPartialCharge.to_stm("constant_current", current=1) - graph_less_interp_points = PolarizedNonSplitPartialCharge.to_stm( + graph_def = PolarizedNonSplitPartialDensity.to_stm("constant_current", current=1) + graph_less_interp_points = PolarizedNonSplitPartialDensity.to_stm( "constant_current", current=1, stm_settings=interp_settings ) assert not np.allclose(graph_def.series.data, graph_less_interp_points.series.data) def test_factory_methods(raw_data, check_factory_methods): - data = raw_data.partial_charge("spin_polarized") - check_factory_methods(calculation.partial_charge, data) + data = raw_data.partial_density("spin_polarized") + check_factory_methods(calculation.partial_density, data) diff --git a/tests/calculation/test_phonon_band.py b/tests/calculation/test_phonon_band.py index fbc13002..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 @@ -88,7 +89,7 @@ def check_series(self, series, projection, label, width): self.Assert.allclose(series.width, width * projection.T) -@patch("py4vasp.calculation._phonon_band.PhononBand.to_graph") +@patch("py4vasp._calculation.phonon_band.PhononBand.to_graph") def test_to_plotly(mock_plot, phonon_band): fig = phonon_band.to_plotly("selection", width=0.2) mock_plot.assert_called_once_with("selection", width=0.2) @@ -104,7 +105,7 @@ def test_to_image(phonon_band): def check_to_image(phonon_band, filename_argument, expected_filename): - with patch("py4vasp.calculation._phonon_band.PhononBand.to_plotly") as plot: + with patch("py4vasp._calculation.phonon_band.PhononBand.to_plotly") as plot: phonon_band.to_image("args", filename=filename_argument, key="word") plot.assert_called_once_with("args", key="word") fig = plot.return_value diff --git a/tests/calculation/test_phonon_dos.py b/tests/calculation/test_phonon_dos.py index 3cefed00..fd6bac2c 100644 --- a/tests/calculation/test_phonon_dos.py +++ b/tests/calculation/test_phonon_dos.py @@ -65,7 +65,7 @@ def check_series(series, reference, label, Assert): Assert.allclose(series.y, reference) -@patch("py4vasp.calculation._phonon_dos.PhononDos.to_graph") +@patch("py4vasp._calculation.phonon_dos.PhononDos.to_graph") def test_phonon_dos_to_plotly(mock_plot, phonon_dos): fig = phonon_dos.to_plotly("selection") mock_plot.assert_called_once_with("selection") @@ -81,7 +81,7 @@ def test_phonon_dos_to_image(phonon_dos): def check_to_image(phonon_dos, filename_argument, expected_filename): - with patch("py4vasp.calculation._phonon_dos.PhononDos.to_plotly") as plot: + with patch("py4vasp._calculation.phonon_dos.PhononDos.to_plotly") as plot: phonon_dos.to_image("args", filename=filename_argument, key="word") plot.assert_called_once_with("args", key="word") fig = plot.return_value diff --git a/tests/calculation/test_projector.py b/tests/calculation/test_projector.py index 4e9baee9..2d501369 100644 --- a/tests/calculation/test_projector.py +++ b/tests/calculation/test_projector.py @@ -6,8 +6,8 @@ import pytest from py4vasp import calculation, exception +from py4vasp._calculation.selection import Selection from py4vasp._util import select -from py4vasp.calculation._selection import Selection @pytest.fixture diff --git a/tests/calculation/test_repr.py b/tests/calculation/test_repr.py index 205ec3d0..d3db9344 100644 --- a/tests/calculation/test_repr.py +++ b/tests/calculation/test_repr.py @@ -1,16 +1,18 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) import importlib +from pathlib import PosixPath, WindowsPath -from py4vasp import calculation +from py4vasp import _calculation, calculation from py4vasp._util import convert def test_repr(): - for name in calculation._quantities: + for name in _calculation.QUANTITIES: instance = getattr(calculation, name) class_name = convert.to_camelcase(name) - module = importlib.import_module(f"py4vasp.calculation._{name}") + module = importlib.import_module(f"py4vasp._calculation.{name}") locals()[class_name] = getattr(module, class_name) + print(repr(instance)) copy = eval(repr(instance)) assert copy.__class__ == instance.__class__ diff --git a/tests/calculation/test_slice_mixin.py b/tests/calculation/test_slice_mixin.py index 81402d21..83f24e1e 100644 --- a/tests/calculation/test_slice_mixin.py +++ b/tests/calculation/test_slice_mixin.py @@ -5,8 +5,8 @@ import pytest from py4vasp import exception +from py4vasp._calculation import slice_ from py4vasp._util import documentation -from py4vasp.calculation import _slice class Other: @@ -16,8 +16,8 @@ def __init__(self, *args, **kwargs): self._kwargs = kwargs -@documentation.format(examples=_slice.examples("example")) -class ExampleSlice(_slice.Mixin, Other): +@documentation.format(examples=slice_.examples("example")) +class ExampleSlice(slice_.Mixin, Other): "{examples}" def steps(self): @@ -175,7 +175,7 @@ def test_incorrect_argument(): def test_documentation(single_step, last_step): - reference = _slice.examples("example") + reference = slice_.examples("example") assert inspect.getdoc(single_step) == reference assert inspect.getdoc(last_step) == reference diff --git a/tests/calculation/test_topology.py b/tests/calculation/test_stoichiometry.py similarity index 54% rename from tests/calculation/test_topology.py rename to tests/calculation/test_stoichiometry.py index 9019fa92..6dc438e8 100644 --- a/tests/calculation/test_topology.py +++ b/tests/calculation/test_stoichiometry.py @@ -3,8 +3,8 @@ import pytest from py4vasp import calculation, exception +from py4vasp._calculation.selection import Selection from py4vasp._util import import_, select -from py4vasp.calculation._selection import Selection ase = import_.optional("ase") pd = import_.optional("pandas") @@ -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,25 @@ 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 +102,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 +125,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 2ee5bbe7..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/calculation/test_workfunction.py b/tests/calculation/test_workfunction.py index ceacf08f..4944c17a 100644 --- a/tests/calculation/test_workfunction.py +++ b/tests/calculation/test_workfunction.py @@ -30,8 +30,8 @@ def test_read(workfunction, Assert): Assert.allclose(actual["average_potential"], workfunction.ref.average_potential) Assert.allclose(actual["vacuum_potential"], workfunction.ref.vacuum_potential) # Uncomment out these lines when vbm and cbm are added to VASP 6.5 - # Assert.allclose(actual["valence_band_maximum"], workfunction.ref.vbm) - # Assert.allclose(actual["conduction_band_minimum"], workfunction.ref.cbm) + Assert.allclose(actual["valence_band_maximum"], workfunction.ref.vbm) + Assert.allclose(actual["conduction_band_minimum"], workfunction.ref.cbm) Assert.allclose(actual["fermi_energy"], workfunction.ref.fermi_energy) @@ -44,7 +44,7 @@ def test_plot(workfunction, Assert): assert graph.series.name == "potential" -@patch("py4vasp.calculation._workfunction.Workfunction.to_graph") +@patch("py4vasp._calculation.workfunction.Workfunction.to_graph") def test_to_plotly(mock_plot, workfunction): fig = workfunction.to_plotly() mock_plot.assert_called_once_with() @@ -60,7 +60,7 @@ def test_to_image(workfunction): def check_to_image(workfunction, filename_argument, expected_filename): - with patch("py4vasp.calculation._workfunction.Workfunction.to_plotly") as plot: + with patch("py4vasp._calculation.workfunction.Workfunction.to_plotly") as plot: workfunction.to_image("args", filename=filename_argument, key="word") plot.assert_called_once_with("args", key="word") fig = plot.return_value @@ -72,18 +72,18 @@ def test_print(workfunction, format_): reference = """\ workfunction along {lattice_vector}: vacuum potential: {vacuum1:.3f} {vacuum2:.3f} - Fermi energy: {fermi_energy:.3f}""" + Fermi energy: {fermi_energy:.3f} + valence band maximum: {vbm:.3f} + conduction band minimum: {cbm:.3f}""" reference = reference.format( lattice_vector=workfunction.ref.lattice_vector, vacuum1=workfunction.ref.vacuum_potential[0], vacuum2=workfunction.ref.vacuum_potential[1], fermi_energy=workfunction.ref.fermi_energy, + vbm=workfunction.ref.vbm, + cbm=workfunction.ref.cbm, ) assert actual == {"text/plain": reference} - # valence band maximum: {vbm:.3f} - # conduction band minimum: {cbm:.3f} - # vbm=workfunction.ref.vbm, - # cbm=workfunction.ref.cbm, def test_factory_methods(raw_data, check_factory_methods): diff --git a/tests/conftest.py b/tests/conftest.py index da06f97f..5defef01 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -133,10 +133,6 @@ def CONTCAR(selection): else: raise exception.NotImplemented() - @staticmethod - def electronic_minimization(selection=None): - return _example_electronic_minimization() - @staticmethod def density(selection): parts = selection.split() @@ -192,6 +188,10 @@ def dos(selection): def elastic_modulus(selection): return _elastic_modulus() + @staticmethod + def electronic_minimization(selection=None): + return _electronic_minimization() + @staticmethod def energy(selection, randomize: bool = False): if selection == "MD": @@ -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() @@ -329,8 +329,8 @@ def workfunction(selection): return _workfunction(selection) @staticmethod - def partial_charge(selection): - return _partial_charge(selection) + def partial_density(selection): + return _partial_density(selection) @pytest.fixture @@ -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(): @@ -545,7 +545,8 @@ def _magnetism(selection): spin_moments=_make_data(np.arange(np.prod(shape)).reshape(shape)), ) if selection == "orbital_moments": - magnetism.orbital_moments = _make_data(np.sqrt(magnetism.spin_moments)) + remove_charge_and_s_component = magnetism.spin_moments[:, 1:, :, 1:] + magnetism.orbital_moments = _make_data(np.sqrt(remove_charge_and_s_component)) return magnetism @@ -667,7 +668,7 @@ def _Sr2TiO4_cell(): ) -def _example_electronic_minimization(): +def _electronic_minimization(): random_convergence_data = np.random.rand(9, 3) iteration_number = np.arange(1, 10)[:, np.newaxis] ncg = np.random.randint(4, 10, (9, 1)) @@ -685,7 +686,7 @@ def _example_electronic_minimization(): ) -def _partial_charge(selection): +def _partial_density(selection): grid_dim = grid_dimensions if "CaAs3_110" in selection: structure = _CaAs3_110_structure() @@ -724,7 +725,7 @@ def _partial_charge(selection): # Fill the gaussian_charge array gaussian_charge[:, :, :, :, gy, gx] = val gaussian_charge = raw.VaspData(gaussian_charge) - return raw.PartialCharge( + return raw.PartialDensity( structure=structure, bands=bands, kpoints=kpoints, @@ -835,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, ) @@ -865,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), ) @@ -880,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"), ) @@ -897,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), ) @@ -912,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"), ) @@ -948,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), ) @@ -963,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"), ) @@ -982,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"), ) @@ -1082,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, ) @@ -1112,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") ) @@ -1148,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_convert.py b/tests/util/test_convert.py index 4e31e835..f6ec203f 100644 --- a/tests/util/test_convert.py +++ b/tests/util/test_convert.py @@ -3,7 +3,7 @@ import numpy as np from py4vasp._config import VASP_COLORS -from py4vasp._util.convert import text_to_string, to_complex, to_rgb +from py4vasp._util.convert import text_to_string, to_camelcase, to_complex, to_rgb def test_text_to_string(): @@ -48,3 +48,11 @@ def test_hex_to_rgb(Assert): expected = np.array(converted) / 255 actual = np.array([to_rgb(color) for color in colors]) Assert.allclose(expected, actual) + + +def test_camelcase(): + assert to_camelcase("foo") == "Foo" + assert to_camelcase("foo_bar") == "FooBar" + assert to_camelcase("foo_bar_baz") == "FooBarBaz" + assert to_camelcase("_foo") == "Foo" + assert to_camelcase("_foo_bar") == "FooBar" diff --git a/tests/util/test_parser.py b/tests/util/test_parser.py index 60c47686..28ba75ae 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, Stoichiometry, Structure 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]) |