def test_scaled_cell_consistency(structure): """Test scaled_cell's PDB-designated validation: inverse of det(SCALE) = Volume of cell""" # Manual calculation of volume = |a_1 . (a_2 x a_3)| a_1 = structure.lattice_vectors[0] a_2 = structure.lattice_vectors[1] a_3 = structure.lattice_vectors[2] a_mid_0 = a_2[1] * a_3[2] - a_2[2] * a_3[1] a_mid_1 = a_2[2] * a_3[0] - a_2[0] * a_3[2] a_mid_2 = a_2[0] * a_3[1] - a_2[1] * a_3[0] volume_from_cellpar = abs(a_1[0] * a_mid_0 + a_1[1] * a_mid_1 + a_1[2] * a_mid_2) scale = scaled_cell(structure.lattice_vectors) volume_from_scale = 1 / numpy.linalg.det(scale) assert volume_from_scale == pytest.approx(volume_from_cellpar)
def test_scaled_cell_and_fractional_coordinates(structures): """Make sure these two different calculations arrive at the same result""" for structure in structures: scale = scaled_cell(structure.lattice_vectors) scale = numpy.asarray(scale) cartesian_positions = numpy.asarray(structure.cartesian_site_positions) scaled_fractional_positions = (scale.T @ cartesian_positions.T).T for i in range(3): scaled_fractional_positions[:, i] %= 1.0 scaled_fractional_positions[:, i] %= 1.0 scaled_fractional_positions = [ tuple(position) for position in scaled_fractional_positions ] calculated_fractional_positions = fractional_coordinates( cell=structure.lattice_vectors, cartesian_positions=structure.cartesian_site_positions, ) for scaled_position, calculated_position in zip( scaled_fractional_positions, calculated_fractional_positions): assert scaled_position == pytest.approx(calculated_position)
def get_pdb( # pylint: disable=too-many-locals optimade_structure: OptimadeStructure, ) -> str: """ Write Protein Data Bank (PDB) structure in the old PDB format from OPTIMADE structure Inspired by `ase.io.proteindatabank.write_proteindatabank()` in the ASE package. :param optimade_structure: OPTIMADE structure :return: str """ if globals().get("np", None) is None: warn(NUMPY_NOT_FOUND) return None pdb = "" attributes = optimade_structure.attributes rotation = None if all(attributes.dimension_types): currentcell = np.asarray(attributes.lattice_vectors) cellpar = cell_to_cellpar(currentcell) exportedcell = cellpar_to_cell(cellpar) rotation = np.linalg.solve(currentcell, exportedcell) # Setting Z-value = 1 and using P1 since we have all atoms defined explicitly Z = 1 spacegroup = "P 1" pdb += ( f"CRYST1{cellpar[0]:9.3f}{cellpar[1]:9.3f}{cellpar[2]:8.3f}" f"{cellpar[3]:7.2f}{cellpar[4]:7.2f}{cellpar[5]:7.2f} {spacegroup:11s}{Z:4d}\n" ) for i, vector in enumerate(scaled_cell(currentcell)): pdb += f"SCALE{i + 1} {vector[0]:10.6f}{vector[1]:10.6f}{vector[2]:10.6f} {0:10.5f}\n" # There is a limit of 5 digit numbers in this field. pdb_maxnum = 100000 bfactor = 1.0 pdb += "MODEL 1\n" species: Dict[str, OptimadeStructureSpecies] = { species.name: species for species in attributes.species } cartesian_site_positions, _ = pad_positions( attributes.cartesian_site_positions) sites = np.asarray(cartesian_site_positions) if rotation is not None: sites = sites.dot(rotation) for site_number in range(attributes.nsites): species_name = attributes.species_at_sites[site_number] site = sites[site_number] current_species = species[species_name] for index, symbol in enumerate(current_species.chemical_symbols): if symbol == "vacancy": continue label = species_name if len(current_species.chemical_symbols) > 1: if ("vacancy" in current_species.chemical_symbols and len(current_species.chemical_symbols) == 2): pass else: label = f"{symbol}{index + 1}" pdb += ( f"ATOM {site_number % pdb_maxnum:5d} {label:4} MOL 1 " f"{site[0]:8.3f}{site[1]:8.3f}{site[2]:8.3f}" f"{current_species.concentration[index]:6.2f}" f"{bfactor:6.2f} {symbol.upper():2} \n") pdb += "ENDMDL\n" return pdb