def parse_resp_esp(f: TextIO) -> EspData: """Parse a file in the .esp file format defined by ``resp`` Parameters ---------- f : TextIO File object opened in read mode containing the .esp file to be parsed. Raises ------ InputFormatError Raised when the file does not follow the expected format. Returns ------- EspData A dataclass representing the information in the given .esp file. """ atom_and_point_count = get_line(f).split() if len(atom_and_point_count) != 2: raise InputFormatError( "Expected atom and point counts on the first line of .esp file in the `resp` format" ) atom_count = int(atom_and_point_count[0]) point_count = int(atom_and_point_count[1]) atoms_coords = [Coords(get_line(f).split()) for _ in range(atom_count)] mesh_coords: List[Coords] = [] esp_values: List[Esp] = [] for _ in range(point_count): val, *coords = get_line(f).split() mesh_coords.append(Coords(coords)) esp_values.append(Esp(val)) field = Field( Mesh( mesh_coords ), esp_values ) return EspData( atoms_coords, field )
def points(self) -> Iterator[Coords]: """Coordinates of points of which the mesh consists The order of iteration is the same as the order of values in a Gaussian "cube" file. Values of `z` are incremented first, then all values of `y`, and finally all values of `x`. This is best described with the following pseudocode:: for x in x_values: for y in y_values: for z in z_values: yield (x, y, z) Yields ------ Iterator[Coords] Iterator over the point coordinates """ for i in range(self.axes[0].point_count): for j in range(self.axes[1].point_count): for k in range(self.axes[2].point_count): yield Coords( (Dist(self.origin[0] + i * self.axes[0].vector[0]), Dist(self.origin[1] + j * self.axes[1].vector[1]), Dist(self.origin[2] + k * self.axes[2].vector[2])))
def parse_axis(line: str) -> GridMesh.Axis: # `cubegen` documentation, which contains the only definition of # the cube format, specifies that a negative `point_count` may be used # in the *input* to signify atomic units. However, this is not an # option in the output of `cubegen` and values are always in atomic units. point_count, *vector_components = line.split() return GridMesh.Axis(Coords(vector_components), int(point_count))
def _parse_atom(line: str) -> AtomWithCoordsAndCharge: line_split = line.split() return AtomWithCoordsAndCharge.from_symbol( line_split[0], Coords(tuple(coord.replace('D', 'E') for coord in line_split[1:4])), Charge(line_split[4].replace('D', 'E')) )
def _parse_grid_prelude(line: str) -> _GridPrelude: line_split = line.split() if len(line_split) in (4, 5): atom_count, *origin_coords = line_split[:4] nval = line_split[4] if len(line_split) == 5 else "1" else: raise InputFormatError( f"Cube file incorrectly formatted! Expected four or five fields " "(atom count, 3*origin coordinates, [NVal]) on line 3, found " "{len(line_split)} fields.") return _GridPrelude(int(atom_count), Coords(origin_coords), int(nval))
def _parse_esp_points(f: TextIO) -> Field[Esp]: points = [] values = [] for line in f: line_split = [val.replace('D', 'E') for val in line.split()] points.append(Coords(line_split[1:4])) values.append(Esp(line_split[0])) return Field( Mesh(points), values )
def _parse_atom(line: str) -> AtomWithCoordsAndCharge: atomic_number, cube_charge, *coords = line.split() return AtomWithCoordsAndCharge(int(atomic_number), Coords(coords), Charge(cube_charge))