gemmi-protools 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of gemmi-protools might be problematic. Click here for more details.

@@ -0,0 +1,177 @@
1
+ """
2
+ @Author: Luo Jiejian
3
+ """
4
+ import gzip
5
+ import io
6
+ import pathlib
7
+ import re
8
+ from collections import defaultdict
9
+ from typing import Dict, Union, List
10
+
11
+ from typeguard import typechecked
12
+
13
+ from .parse_pdb_header import _parse_pdb_header_list
14
+ from .struct_info import Entity
15
+
16
+
17
+ @typechecked
18
+ def _molecule_information(header_dict: Dict) -> Entity:
19
+ entity2description = dict()
20
+ entity2species = dict()
21
+ entity2taxid = dict()
22
+
23
+ for idx in header_dict["compound"].keys():
24
+ compound = header_dict["compound"][idx]
25
+ if "chain" in compound:
26
+ chain = re.sub(pattern=r"\s+", repl="", string=compound["chain"])
27
+ if chain != "":
28
+ tmp = chain.split(",")
29
+ tmp.sort()
30
+ key = ",".join(tmp)
31
+
32
+ molecule = compound.get("molecule", "")
33
+
34
+ if idx in header_dict["source"]:
35
+ source = header_dict["source"][idx]
36
+ specie = source.get("organism_scientific", "")
37
+ taxid = source.get("organism_taxid", "")
38
+ else:
39
+ specie = ""
40
+ taxid = ""
41
+
42
+ entity2description[key] = molecule
43
+ entity2species[key] = specie
44
+ entity2taxid[key] = taxid
45
+
46
+ vals = dict(eid2desc=entity2description,
47
+ eid2specie=entity2species,
48
+ eid2taxid=entity2taxid,
49
+ polymer2eid=dict()
50
+ )
51
+ return Entity(**vals)
52
+
53
+
54
+ @typechecked
55
+ def _is_pdb(path: Union[str, pathlib.Path]) -> bool:
56
+ if isinstance(path, str):
57
+ path = pathlib.Path(path)
58
+ if path.suffixes:
59
+ if path.suffixes[-1] == ".pdb":
60
+ return True
61
+ elif "".join(path.suffixes[-2:]) == ".pdb.gz":
62
+ return True
63
+ else:
64
+ return False
65
+ else:
66
+ return False
67
+
68
+
69
+ # add by Ljj
70
+ @typechecked
71
+ def _pdb_entity_info(path: Union[str, pathlib.Path]) -> Entity:
72
+ if _is_pdb(path):
73
+ cur_path = pathlib.Path(path)
74
+ if cur_path.suffixes[-1] == ".pdb":
75
+ with open(path, "r") as text_io:
76
+ lines = text_io.readlines()
77
+ else:
78
+ with gzip.open(path, "rb") as gz_handle:
79
+ with io.TextIOWrapper(gz_handle, encoding="utf-8") as text_io:
80
+ lines = text_io.readlines()
81
+ else:
82
+ raise ValueError("Only support .pdb or .pdb.gz file, but got %s" % path)
83
+
84
+ i = 0
85
+ for i in range(len(lines)):
86
+ line = lines[i]
87
+ record_type = line[0:6]
88
+ if record_type in ("ATOM ", "HETATM", "MODEL "):
89
+ break
90
+
91
+ header = lines[0:i]
92
+ info = _parse_pdb_header_list(header)
93
+ return _molecule_information(info)
94
+
95
+
96
+ @typechecked
97
+ def _get_pdb_resolution(remark_lines: List[str]) -> float:
98
+ resolutions = []
99
+ for line in remark_lines:
100
+ tmp = re.search(r"REMARK.+RESOLUTION.+?([\d\.]+|NOT APPLICABLE)", line)
101
+ if tmp:
102
+ v = tmp.groups()[0]
103
+ try:
104
+ vf = float(v)
105
+ except (TypeError, ValueError):
106
+ continue
107
+ else:
108
+ resolutions.append(vf)
109
+ if resolutions:
110
+ return min(resolutions)
111
+ else:
112
+ return 0.0
113
+
114
+
115
+ @typechecked
116
+ def _compound_source_string(entity: Entity) -> List[str]:
117
+ entity2polymer = defaultdict(list)
118
+ for k, v in entity["polymer2eid"].items():
119
+ entity2polymer[v].append(k)
120
+ entity_labels = list(entity2polymer.keys())
121
+ entity_labels.sort()
122
+
123
+ values = []
124
+ for i, el in enumerate(entity_labels):
125
+ values.append(dict(mol_id=str(i + 1),
126
+ chain=", ".join(entity2polymer[el]),
127
+ molecule=entity["eid2desc"].get(el, "?"),
128
+ organism_scientific=entity["eid2specie"].get(el, "?"),
129
+ organism_taxid=entity["eid2taxid"].get(el, "?")
130
+ )
131
+ )
132
+ outputs = []
133
+ # compound
134
+ compound_mol0 = "COMPND MOL_ID: {mol_id};"
135
+ compound_mol1 = "COMPND {n_line:>3} MOL_ID: {mol_id};"
136
+ compound_molecule = "COMPND {n_line:>3} MOLECULE: {molecule};"
137
+ compound_chain = "COMPND {n_line:>3} CHAIN: {chain};"
138
+
139
+ i = 0
140
+ for val in values:
141
+ if i == 0:
142
+ outputs.append(compound_mol0.format(**val))
143
+ i += 1
144
+ for c_str in [compound_molecule, compound_chain]:
145
+ cur_val = val.copy()
146
+ cur_val["n_line"] = i
147
+ outputs.append(c_str.format(**cur_val))
148
+ i += 1
149
+ else:
150
+ for c_str in [compound_mol1, compound_molecule, compound_chain]:
151
+ cur_val = val.copy()
152
+ cur_val["n_line"] = i
153
+ outputs.append(c_str.format(**cur_val))
154
+ i += 1
155
+
156
+ source_mol0 = "SOURCE MOL_ID: {mol_id};"
157
+ source_mol1 = "SOURCE {n_line:>3} MOL_ID: {mol_id};"
158
+ source_scientific = "SOURCE {n_line:>3} ORGANISM_SCIENTIFIC: {organism_scientific};"
159
+ source_taxid = "SOURCE {n_line:>3} ORGANISM_TAXID: {organism_taxid};"
160
+
161
+ i = 0
162
+ for val in values:
163
+ if i == 0:
164
+ outputs.append(source_mol0.format(**val))
165
+ i += 1
166
+ for c_str in [source_scientific, source_taxid]:
167
+ cur_val = val.copy()
168
+ cur_val["n_line"] = i
169
+ outputs.append(c_str.format(**cur_val))
170
+ i += 1
171
+ else:
172
+ for c_str in [source_mol1, source_scientific, source_taxid]:
173
+ cur_val = val.copy()
174
+ cur_val["n_line"] = i
175
+ outputs.append(c_str.format(**cur_val))
176
+ i += 1
177
+ return outputs
gemmi_protools/ppi.py ADDED
@@ -0,0 +1,74 @@
1
+ """
2
+ @Author: Luo Jiejian
3
+ """
4
+ import pathlib
5
+ from typing import Union, List
6
+
7
+ import numpy as np
8
+ from scipy.spatial import cKDTree
9
+
10
+ from .reader import StructureParser
11
+
12
+
13
+ def _ppi_atoms(struct, chains):
14
+ """
15
+ Load atoms for N and O of backbone and N, O, P, S of side chains, only for PPI searching
16
+ :param struct:
17
+ :param chains:
18
+ :return:
19
+ """
20
+ protein_atoms = ['N', 'ND1', 'ND2', 'NE', 'NE1', 'NE2', 'NH1', 'NH2', 'NZ',
21
+ 'O', 'OD1', 'OD2', 'OE1', 'OE2', 'OG', 'OG1', 'OH',
22
+ 'SD', 'SG']
23
+ xna_atoms = ['N1', 'N2', 'N3', 'N4', 'N6', 'N7', 'N9',
24
+ 'O2', "O2'", "O3'", 'O4', "O4'", "O5'", 'O6',
25
+ 'OP1', 'OP2', 'OP3', 'P']
26
+ pro_chs = []
27
+ xna_chs = []
28
+ for c in chains:
29
+ t = struct.chain_types.get(c, "")
30
+ if t == "protein":
31
+ pro_chs.append(c)
32
+ elif t in ["dna", "rna"]:
33
+ xna_chs.append(c)
34
+
35
+ pro_coord, pro_id = struct.get_atom_coords(pro_chs, protein_atoms)
36
+ xna_coord, xna_id = struct.get_atom_coords(xna_chs, xna_atoms)
37
+ return np.concatenate([pro_coord, xna_coord], axis=0), np.concatenate([pro_id, xna_id], axis=0)
38
+
39
+
40
+ def ppi_interface_residues(in_file: Union[str, pathlib.Path],
41
+ chains_x: List[str],
42
+ chains_y: List[str],
43
+ threshold: float = 4.0):
44
+ """
45
+ identify PPI among protein, DNA, RNA
46
+ :param in_file:
47
+ :param chains_x:
48
+ :param chains_y:
49
+ :param threshold:
50
+ :return:
51
+ PPI residues of chains_x, PPI residues of chains_y
52
+ """
53
+
54
+ st = StructureParser()
55
+ st.load_from_file(in_file)
56
+ st.set_default_model()
57
+ st.STRUCT.remove_alternative_conformations()
58
+ st.STRUCT.remove_ligands_and_waters()
59
+ st.STRUCT.remove_hydrogens()
60
+ st.STRUCT.remove_empty_chains()
61
+ st.update_entity()
62
+
63
+ x_coord, x_id = _ppi_atoms(st, chains_x)
64
+ y_coord, y_id = _ppi_atoms(st, chains_y)
65
+
66
+ kd_tree_x = cKDTree(x_coord)
67
+ kd_tree_y = cKDTree(y_coord)
68
+
69
+ pairs = kd_tree_x.sparse_distance_matrix(kd_tree_y, threshold, output_type='coo_matrix')
70
+
71
+ x_res = np.unique(x_id[pairs.row][["ch_name", 'res_num', 'res_icode', 'res_name']])
72
+ y_res = np.unique(y_id[pairs.col][["ch_name", 'res_num', 'res_icode', 'res_name']])
73
+
74
+ return x_res, y_res
@@ -0,0 +1,371 @@
1
+ """
2
+ @Author: Luo Jiejian
3
+ """
4
+ import pathlib
5
+ import re
6
+ import string
7
+ import warnings
8
+ from copy import deepcopy
9
+ from typing import Union, Optional, List
10
+
11
+ import gemmi
12
+ import numpy as np
13
+ from typeguard import typechecked
14
+
15
+ from .cif_opts import _cif_block_for_output, _is_cif
16
+ from .parser import (_assign_digital_entity_names, _ent_from_structure,
17
+ pdb_parser, cif_parser, _chain_type, _chain_names2one_letter,
18
+ _assert_unique_chain_names_in_models, get_assembly)
19
+ from .pdb_opts import _compound_source_string, _is_pdb
20
+ from .struct_info import Info
21
+
22
+
23
+ class StructureParser(object):
24
+ """
25
+ Enhance Structure reader for .cif, .cif.gz, .pdb or .pdb.gz
26
+ """
27
+
28
+ def __init__(self, structure: gemmi.Structure = None):
29
+ if not isinstance(structure, (type(None), gemmi.Structure)):
30
+ raise ValueError("structure must be gemmi.Structure or None")
31
+ if structure is None:
32
+ self.STRUCT = gemmi.Structure()
33
+ elif isinstance(structure, gemmi.Structure):
34
+ _assert_unique_chain_names_in_models(structure)
35
+ self.STRUCT = structure.clone()
36
+ else:
37
+ raise ValueError("structure must be gemmi.Structure or None")
38
+ self.STRUCT.setup_entities()
39
+ _assign_digital_entity_names(self.STRUCT)
40
+
41
+ self.INFO = Info()
42
+ self.INFO.from_gemmi_structure_infomap(self.STRUCT.info)
43
+ self.ENTITY = _ent_from_structure(self.STRUCT)
44
+ self.update_entity()
45
+
46
+ @typechecked
47
+ def load_from_file(self, path: Union[str, pathlib.PosixPath]):
48
+ if _is_pdb(path):
49
+ struct, entity = pdb_parser(path)
50
+ elif _is_cif(path):
51
+ struct, entity = cif_parser(path)
52
+ else:
53
+ raise ValueError("Only support .cif, .cif.gz, .pdb or .pdb.gz file, but got %s" % path)
54
+
55
+ _assert_unique_chain_names_in_models(struct)
56
+ self.STRUCT, self.ENTITY = struct, entity
57
+ self.INFO.from_gemmi_structure_infomap(self.STRUCT.info)
58
+ self.update_entity()
59
+
60
+ @typechecked
61
+ def to_pdb(self, outfile: str, write_minimal_pdb=False):
62
+ compound_source = _compound_source_string(self.ENTITY)
63
+ struct = self.STRUCT.clone()
64
+ struct.raw_remarks = compound_source + struct.raw_remarks
65
+ if write_minimal_pdb:
66
+ struct.write_minimal_pdb(outfile)
67
+ else:
68
+ struct.write_pdb(outfile)
69
+
70
+ @typechecked
71
+ def to_cif(self, outfile: str):
72
+ out_block = _cif_block_for_output(self.STRUCT, self.ENTITY)
73
+ out_block.write_file(outfile)
74
+
75
+ @property
76
+ def chain_ids(self):
77
+ vals = []
78
+ for m in self.STRUCT:
79
+ for c in m:
80
+ vals.append(c.name)
81
+ vals.sort()
82
+ return vals
83
+
84
+ @property
85
+ def model_numbers(self):
86
+ return [m.num for m in self.STRUCT]
87
+
88
+ @typechecked
89
+ def set_default_model(self, num: Optional[int] = None):
90
+ """
91
+ Set the first model as default
92
+ :param num:
93
+ :return:
94
+ """
95
+ if len(self.STRUCT) == 0:
96
+ raise RuntimeError("There is no model in structure")
97
+
98
+ keep_model = None
99
+ if num is None:
100
+ # default first model
101
+ keep_model = self.STRUCT[0]
102
+ else:
103
+ for model in self.STRUCT:
104
+ if model.num == num:
105
+ keep_model = model
106
+ break
107
+
108
+ if keep_model is None:
109
+ raise RuntimeError("Model %d not found in structure" % num)
110
+
111
+ # del, reversed order indexes
112
+ indexes_to_del = [i for i, model in enumerate(self.STRUCT) if model.num != keep_model.num]
113
+ indexes_to_del.sort(reverse=True)
114
+
115
+ for cur_index in indexes_to_del:
116
+ del self.STRUCT[cur_index]
117
+
118
+ @property
119
+ def chain_types(self):
120
+ return {c: _chain_type(self.STRUCT, c) for c in self.chain_ids}
121
+
122
+ @property
123
+ def assembly_names(self):
124
+ return [assem.name for assem in self.STRUCT.assemblies]
125
+
126
+ @property
127
+ def polymer_sequences(self):
128
+ cts = self.chain_types
129
+ out = dict()
130
+ for model in self.STRUCT:
131
+ for chain in model:
132
+ ct = cts.get(chain.name, "other")
133
+ if ct != "other":
134
+ out[chain.name] = re.sub("-", "", chain.get_polymer().make_one_letter_sequence())
135
+ return out
136
+
137
+ @property
138
+ def polymer_residue_numbers(self):
139
+ cts = self.chain_types
140
+ out = dict()
141
+ id_type = np.dtype([
142
+ ("ch_name", "U5"),
143
+ ("res_num", "i4"),
144
+ ("res_icode", "U3"),
145
+ ("res_name", "U5"),
146
+ ])
147
+ for model in self.STRUCT:
148
+ for chain in model:
149
+ ct = cts.get(chain.name, "other")
150
+ if ct != "other":
151
+ out[chain.name] = np.array([(chain.name, r.seqid.num, r.seqid.icode, r.name)
152
+ for r in chain.get_polymer()], dtype=id_type)
153
+ return out
154
+
155
+ @property
156
+ def chain_residues(self):
157
+ out = dict()
158
+ for model in self.STRUCT:
159
+ for chain in model:
160
+ out[chain.name] = [r.name for r in chain]
161
+ return out
162
+
163
+ def update_entity(self):
164
+ """
165
+ Update ENTITY, .entities .assemblies according to subchains
166
+ :return:
167
+ """
168
+ subchains = []
169
+ for model in self.STRUCT:
170
+ for chain in model:
171
+ subchains.extend([sc.subchain_id() for sc in chain.subchains()])
172
+
173
+ # update .entities
174
+ new_entities = gemmi.EntityList()
175
+ ent_names = [] # keep
176
+ for ent in self.STRUCT.entities:
177
+ tmp = [i for i in ent.subchains if i in subchains]
178
+ if tmp:
179
+ ent.subchains = tmp
180
+ new_entities.append(ent)
181
+ ent_names.append(ent.name)
182
+ self.STRUCT.entities = new_entities
183
+
184
+ # update .ENTITY
185
+ for super_key in ["eid2desc", "eid2specie", "eid2taxid"]:
186
+ for eid in list(self.ENTITY[super_key].keys()):
187
+ if eid not in ent_names:
188
+ del self.ENTITY[super_key][eid]
189
+
190
+ for cid, eid in list(self.ENTITY["polymer2eid"].items()):
191
+ if eid not in ent_names or cid not in self.chain_ids:
192
+ del self.ENTITY["polymer2eid"][cid]
193
+
194
+ # update .assemblies
195
+ all_cid = self.chain_ids
196
+ del_assembly_indexes = []
197
+
198
+ for a_i, assembly in enumerate(self.STRUCT.assemblies):
199
+ del_gen_indexes = []
200
+ for g_i, gen in enumerate(assembly.generators):
201
+ # chains
202
+ tmp1 = [i for i in gen.chains if i in all_cid]
203
+ gen.chains = tmp1
204
+
205
+ tmp2 = [i for i in gen.subchains if i in subchains]
206
+ gen.subchains = tmp2
207
+ # empty gen
208
+ if gen.chains == [] and gen.subchains == []:
209
+ del_gen_indexes.append(g_i)
210
+
211
+ del_gen_indexes.sort(reverse=True)
212
+ for dgi in del_gen_indexes:
213
+ del assembly.generators[dgi]
214
+
215
+ if len(del_gen_indexes) == len(assembly.generators):
216
+ del_assembly_indexes.append(a_i)
217
+
218
+ del_assembly_indexes.sort(reverse=True)
219
+ for dai in del_assembly_indexes:
220
+ del self.STRUCT.assemblies[dai]
221
+
222
+ @typechecked
223
+ def rename_chain(self, origin_name: str, target_name: str):
224
+ if origin_name not in self.chain_ids:
225
+ raise ValueError("chain %s not found" % origin_name)
226
+ other_chain_names = set(self.chain_ids) - {origin_name}
227
+
228
+ if target_name in other_chain_names:
229
+ raise ValueError("target chain name %s has existed, change to a different one." % target_name)
230
+
231
+ self.STRUCT.rename_chain(origin_name, target_name)
232
+
233
+ # update .polymer2eid if exist
234
+ if origin_name in self.ENTITY.polymer2eid:
235
+ val = self.ENTITY.polymer2eid[origin_name]
236
+ del self.ENTITY.polymer2eid[origin_name]
237
+ self.ENTITY.polymer2eid[target_name] = val
238
+
239
+ # update .assemblies.generator.chain if exists, for .pdb loading structure
240
+ for assembly in self.STRUCT.assemblies:
241
+ for gen in assembly.generators:
242
+ tmp = [target_name if c == origin_name else c for c in gen.chains]
243
+ gen.chains = tmp
244
+
245
+ @typechecked
246
+ def switch_chain_names(self, chain_name_1: str, chain_name_2: str):
247
+ if chain_name_1 not in self.chain_ids:
248
+ raise ValueError("chain_name_2 %s not in structure" % chain_name_1)
249
+ if chain_name_2 not in self.chain_ids:
250
+ raise ValueError("chain_name_2 %s not in structure" % chain_name_2)
251
+
252
+ l3 = [i + j + k for i in string.ascii_uppercase for j in string.ascii_uppercase for k in string.ascii_uppercase]
253
+ l3.sort(reverse=True)
254
+
255
+ current_names = set(self.chain_ids)
256
+ l3_l = [n for n in l3 if n not in current_names]
257
+ sw_name = l3_l.pop()
258
+ self.rename_chain(chain_name_1, sw_name)
259
+ self.rename_chain(chain_name_2, chain_name_1)
260
+ self.rename_chain(sw_name, chain_name_2)
261
+
262
+ @typechecked
263
+ def pick_chains(self, chain_names: List[str]):
264
+ self.set_default_model()
265
+
266
+ if chain_names:
267
+ missing = [c for c in chain_names if c not in self.chain_ids]
268
+ if missing:
269
+ raise ValueError("Chains %s not found" % ",".join(missing))
270
+ else:
271
+ del_chain_names = set(self.chain_ids) - set(chain_names)
272
+ del_chain_indexes = [i for i, ch in enumerate(self.STRUCT[0]) if ch.name in del_chain_names]
273
+ del_chain_indexes.sort(reverse=True)
274
+ for di in del_chain_indexes:
275
+ del self.STRUCT[0][di]
276
+ self.update_entity()
277
+ else:
278
+ raise ValueError("No chain is given")
279
+
280
+ @typechecked
281
+ def make_chain_names_to_one_letter(self, only_uppercase: bool = True):
282
+ _mapper = _chain_names2one_letter(self.STRUCT, only_uppercase)
283
+ for origin_name, target_name in _mapper.items():
284
+ self.rename_chain(origin_name, target_name)
285
+ return _mapper
286
+
287
+ @typechecked
288
+ def get_assembly(self, assembly_name: str):
289
+ if assembly_name not in self.assembly_names:
290
+ raise ValueError("assembly %s is not found" % assembly_name)
291
+
292
+ struct, polymer2eid = get_assembly(self.STRUCT, assembly_name, gemmi.HowToNameCopiedChain.Short)
293
+ out = StructureParser(struct)
294
+ out.ENTITY = deepcopy(self.ENTITY)
295
+ out.ENTITY.polymer2eid = polymer2eid
296
+
297
+ # update info
298
+ prefix = "[Assembly %s] " % assembly_name
299
+ out.INFO.title = prefix + out.INFO.title
300
+ out.STRUCT.info = out.INFO.to_gemmi_structure_infomap()
301
+ return out
302
+
303
+ @typechecked
304
+ def merge_chains(self, chains: List[str]):
305
+ """
306
+ Merge a list of chains, target chain id is chains[0]
307
+
308
+ Renumber the new chain from 1
309
+
310
+ [No fix the Entity and some other information of structure]
311
+ :param chains:
312
+ :return:
313
+ GemmiLoader
314
+ """
315
+ for c in chains:
316
+ if c not in self.chain_ids:
317
+ raise RuntimeError("Chain %s is not in the structure" % c)
318
+ if len(self.STRUCT) > 1:
319
+ print("Multiple models in structure, do nothing")
320
+ elif len(chains) < 2:
321
+ print("Query chains less than 2, do nothing")
322
+ else:
323
+ new_chain = gemmi.Chain(chains[0])
324
+ residue_index = 1
325
+
326
+ model = self.STRUCT[0]
327
+
328
+ for ch in model:
329
+ if ch.name in chains:
330
+ for res in ch:
331
+ nr = deepcopy(res)
332
+ nr.seqid.icode = " "
333
+ nr.seqid.num = residue_index
334
+ new_chain.add_residue(nr)
335
+ residue_index += 1
336
+
337
+ for c in chains:
338
+ self.STRUCT[0].remove_chain(c)
339
+
340
+ self.STRUCT[0].add_chain(new_chain, unique_name=True)
341
+
342
+ def get_atom_coords(self, chains: List[str], atoms: Optional[List[str]] = None):
343
+ for c in chains:
344
+ if c not in self.chain_ids:
345
+ warnings.warn("Chain %s is not in the structure" % c)
346
+
347
+ coord = []
348
+ atom_id = []
349
+ id_type = np.dtype([
350
+ ("ch_name", "U5"),
351
+ ("res_num", "i4"),
352
+ ("res_icode", "U3"),
353
+ ("res_name", "U5"),
354
+ ("atom_name", "U5")
355
+ ])
356
+
357
+ model = self.STRUCT[0]
358
+ for ch in model:
359
+ if ch.name in chains:
360
+ for res in ch:
361
+ for atom in res:
362
+ if atoms is None or atom.name in atoms:
363
+ cur_id = (ch.name, res.seqid.num, res.seqid.icode, res.name, atom.name)
364
+ cur_pos = atom.pos.tolist()
365
+ coord.append(cur_pos)
366
+ atom_id.append(cur_id)
367
+
368
+ if coord:
369
+ return np.array(coord, dtype=np.float32), np.array(atom_id, dtype=id_type)
370
+ else:
371
+ return np.empty(shape=(0, 3), dtype=np.float32), np.array(atom_id, dtype=id_type)