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)
Exemplo n.º 2
0
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)
Exemplo n.º 3
0
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