File: cif_write.py

package info (click to toggle)
python-shelxfile 22-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 6,652 kB
  • sloc: python: 6,347; sh: 11; makefile: 5
file content (138 lines) | stat: -rw-r--r-- 5,048 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
import datetime
from pathlib import Path
from string import Template
from typing import TYPE_CHECKING, Dict
from shelxfile.version import VERSION
from shelxfile.atoms.atoms import Atoms

if TYPE_CHECKING:
    from shelxfile.shelx.shelxfile import Shelxfile


class CifFile():
    """Class for writing IUCr CIF-1.1 of DDL1 compliant Version 2.4.5 files."""

    def __init__(self, shelx_file: 'Shelxfile', template: str = None):
        if template is not None:
            self.template = template
        else:
            self.template = Path(__file__).parent / "cif_template.tmpl"
        self.data = shelx_file
        self._cif = None

    def __str__(self):
        return self._cif

    def __repr__(self):
        return self._cif

    def write_cif(self, cif_path: Path) -> None:
        with open(self.template, "r") as f:
            template = f.read()
        cif = Template(template)
        sub = cif.substitute(self._cif_dict())
        cif_path.write_text(sub)

    def _cif_dict(self) -> Dict[str, str]:
        cif_dict = {}
        cif_dict["data_name"] = self.data.titl.split()[0].lower() or "unknown"
        cif_dict["version"] = VERSION
        cif_dict["creation_date"] = f"{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}"
        cif_dict["sum_formula"] = self.data.sum_formula
        cif_dict["formula_weight"] = self.data.formula_weight
        cif_dict.update(self._cell_data())
        cif_dict.update(self._symmetry_data())
        cif_dict.update(self._atoms_data())
        cif_dict.update(self._adp_data())
        cif_dict.update(self._misc_dict())
        return cif_dict

    def _cell_data(self) -> Dict[str, float]:
        cell = self.data.cell
        return {
            "cell_a"     : cell.a,
            "cell_b"     : cell.b,
            "cell_c"     : cell.c,
            "cell_alpha" : cell.alpha,
            "cell_beta"  : cell.beta,
            "cell_gamma" : cell.gamma,
            "cell_volume": round(cell.volume, 4),
            "cell_z"     : self.data.zerr.Z,
        }

    def _symmetry_data(self) -> Dict[str, str]:
        symmcards_ = [f" '{sym.to_cif()}'" for sym in self.data.symmcards]
        return {
            "space_group"  : self.data.space_group,
            # "crystal_system": self.data.symmcards.crystal_system,
            "symmetry_loop": '\n'.join(symmcards_),
        }

    def _atoms_data(self) -> Dict[str, str]:
        atoms: Atoms = self.data.atoms
        lines = []
        loop_header = ("loop_\n"
                       " _atom_site_label\n"
                       " _atom_site_type_symbol\n"
                       " _atom_site_fract_x\n"
                       " _atom_site_fract_y\n"
                       " _atom_site_fract_z\n"
                       " _atom_site_adp_type\n"
                       " _atom_site_occupancy\n"
                       " _atom_site_disorder_group")
        for atom in atoms.all_atoms:
            if not atom.qpeak:
                lines.append(f"{atom.fullname_short} {atom.element} "
                             f"{atom.x} {atom.y} {atom.z} "
                             f"{'Uiso' if atom.is_isotropic else 'Uani'} "
                             f"{atom.occupancy} {atom.part.n}")
        return {
            "atom_loop_header": loop_header,
            "atom_loop"       : '\n'.join(lines),
        }

    def _adp_data(self) -> Dict[str, str]:
        atoms: Atoms = self.data.atoms
        lines = []
        loop_header = (
            "loop_\n"
            " _atom_site_aniso_label\n"
            " _atom_site_aniso_U_11\n"
            " _atom_site_aniso_U_22\n"
            " _atom_site_aniso_U_33\n"
            " _atom_site_aniso_U_23\n"
            " _atom_site_aniso_U_13\n"
            " _atom_site_aniso_U_12")
        for atom in atoms.all_atoms:
            if not atom.qpeak and not atom.is_isotropic:
                lines.append(f"{atom.fullname_short} "
                             f"{atom.U11} {atom.U22} {atom.U33} "
                             f"{atom.U23} {atom.U13} {atom.U12} ")
        return {
            "aniso_loop_header": loop_header,
            "aniso_loop"       : '\n'.join(lines),
        }

    def _misc_dict(self) -> Dict[str, str]:
        misc_dict = {}
        misc_dict["temperature"] = round(self.data.temp_in_kelvin, 3) or '?'
        misc_dict["crystal_size_max"] = self.data.size.max or '?'
        misc_dict["crystal_size_mid"] = self.data.size.mid or '?'
        misc_dict["crystal_size_min"] = self.data.size.min or '?'
        misc_dict["wavelength"] = self.data.wavelength or '?'
        misc_dict["R1"] = self.data.R1 or '?'
        misc_dict["wR2"] = self.data.wr2 or '?'
        misc_dict["goodness_of_fit"] = self.data.goof or '?'
        return misc_dict


if __name__ == '__main__':
    from shelxfile import Shelxfile

    shx = Shelxfile(debug=True)
    #shx.read_file('tests/resources/I-43d.res')
    shx.read_file('./tests/resources/p21c.res')

    #cif = CifFile(shx)
    #cif.write_cif(Path('p21c-test.cif'))
    shx.to_cif('p21c-test.cif')