biotite 1.2.0__cp312-cp312-win_amd64.whl → 1.4.0__cp312-cp312-win_amd64.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.
Files changed (62) hide show
  1. biotite/application/viennarna/rnaplot.py +7 -7
  2. biotite/interface/openmm/__init__.py +4 -0
  3. biotite/interface/pymol/__init__.py +3 -0
  4. biotite/interface/pymol/object.py +3 -1
  5. biotite/interface/rdkit/__init__.py +4 -0
  6. biotite/interface/rdkit/mol.py +5 -5
  7. biotite/interface/version.py +23 -0
  8. biotite/sequence/align/banded.cp312-win_amd64.pyd +0 -0
  9. biotite/sequence/align/banded.pyx +1 -1
  10. biotite/sequence/align/kmeralphabet.cp312-win_amd64.pyd +0 -0
  11. biotite/sequence/align/kmersimilarity.cp312-win_amd64.pyd +0 -0
  12. biotite/sequence/align/kmertable.cp312-win_amd64.pyd +0 -0
  13. biotite/sequence/align/localgapped.cp312-win_amd64.pyd +0 -0
  14. biotite/sequence/align/localungapped.cp312-win_amd64.pyd +0 -0
  15. biotite/sequence/align/multiple.cp312-win_amd64.pyd +0 -0
  16. biotite/sequence/align/multiple.pyx +1 -2
  17. biotite/sequence/align/pairwise.cp312-win_amd64.pyd +0 -0
  18. biotite/sequence/align/pairwise.pyx +2 -4
  19. biotite/sequence/align/permutation.cp312-win_amd64.pyd +0 -0
  20. biotite/sequence/align/selector.cp312-win_amd64.pyd +0 -0
  21. biotite/sequence/align/tracetable.cp312-win_amd64.pyd +0 -0
  22. biotite/sequence/codec.cp312-win_amd64.pyd +0 -0
  23. biotite/sequence/phylo/nj.cp312-win_amd64.pyd +0 -0
  24. biotite/sequence/phylo/tree.cp312-win_amd64.pyd +0 -0
  25. biotite/sequence/phylo/upgma.cp312-win_amd64.pyd +0 -0
  26. biotite/structure/basepairs.py +13 -14
  27. biotite/structure/bonds.cp312-win_amd64.pyd +0 -0
  28. biotite/structure/bonds.pyx +67 -6
  29. biotite/structure/box.py +141 -3
  30. biotite/structure/celllist.cp312-win_amd64.pyd +0 -0
  31. biotite/structure/celllist.pyx +0 -1
  32. biotite/structure/chains.py +15 -21
  33. biotite/structure/charges.cp312-win_amd64.pyd +0 -0
  34. biotite/structure/compare.py +2 -0
  35. biotite/structure/dotbracket.py +4 -4
  36. biotite/structure/graphics/rna.py +19 -16
  37. biotite/structure/hbond.py +1 -2
  38. biotite/structure/info/components.bcif +0 -0
  39. biotite/structure/io/pdb/convert.py +84 -2
  40. biotite/structure/io/pdb/file.py +94 -7
  41. biotite/structure/io/pdb/hybrid36.cp312-win_amd64.pyd +0 -0
  42. biotite/structure/io/pdbx/bcif.py +6 -3
  43. biotite/structure/io/pdbx/cif.py +5 -2
  44. biotite/structure/io/pdbx/compress.py +71 -34
  45. biotite/structure/io/pdbx/convert.py +226 -58
  46. biotite/structure/io/pdbx/encoding.cp312-win_amd64.pyd +0 -0
  47. biotite/structure/io/pdbx/encoding.pyx +39 -23
  48. biotite/structure/pseudoknots.py +6 -6
  49. biotite/structure/residues.py +10 -27
  50. biotite/structure/rings.py +118 -2
  51. biotite/structure/sasa.cp312-win_amd64.pyd +0 -0
  52. biotite/structure/sasa.pyx +28 -29
  53. biotite/structure/segments.py +55 -0
  54. biotite/structure/spacegroups.json +1567 -0
  55. biotite/structure/spacegroups.license +26 -0
  56. biotite/structure/superimpose.py +1 -191
  57. biotite/structure/transform.py +220 -1
  58. biotite/version.py +2 -2
  59. {biotite-1.2.0.dist-info → biotite-1.4.0.dist-info}/METADATA +4 -34
  60. {biotite-1.2.0.dist-info → biotite-1.4.0.dist-info}/RECORD +62 -60
  61. {biotite-1.2.0.dist-info → biotite-1.4.0.dist-info}/WHEEL +1 -1
  62. {biotite-1.2.0.dist-info → biotite-1.4.0.dist-info}/licenses/LICENSE.rst +0 -0
@@ -22,23 +22,23 @@ __all__ = [
22
22
  "chain_iter",
23
23
  ]
24
24
 
25
- import numpy as np
26
25
  from biotite.structure.segments import (
27
26
  apply_segment_wise,
28
27
  get_segment_masks,
29
28
  get_segment_positions,
29
+ get_segment_starts,
30
30
  get_segment_starts_for,
31
31
  segment_iter,
32
32
  spread_segment_wise,
33
33
  )
34
34
 
35
35
 
36
- def get_chain_starts(array, add_exclusive_stop=False):
36
+ def get_chain_starts(array, add_exclusive_stop=False, extra_categories=()):
37
37
  """
38
38
  Get the indices in an atom array, which indicates the beginning of
39
39
  a new chain.
40
40
 
41
- A new chain starts, when the chain ID changes or when the residue ID
41
+ A new chain starts, when the chain or sym ID changes or when the residue ID
42
42
  decreases.
43
43
 
44
44
  Parameters
@@ -49,6 +49,9 @@ def get_chain_starts(array, add_exclusive_stop=False):
49
49
  If true, the exclusive stop of the input atom array, i.e.
50
50
  ``array.array_length()``, is added to the returned array of
51
51
  start indices as last element.
52
+ extra_categories : tuple of str, optional
53
+ Additional annotation categories that induce the start of a new chain,
54
+ when their value change from one atom to the next.
52
55
 
53
56
  Returns
54
57
  -------
@@ -60,24 +63,15 @@ def get_chain_starts(array, add_exclusive_stop=False):
60
63
  This method is internally used by all other chain-related
61
64
  functions.
62
65
  """
63
- if array.array_length() == 0:
64
- return np.array([], dtype=int)
65
-
66
- diff = np.diff(array.res_id)
67
- res_id_decrement = diff < 0
68
- # This mask is 'true' at indices where the value changes
69
- chain_id_changes = array.chain_id[1:] != array.chain_id[:-1]
70
-
71
- # Convert mask to indices
72
- # Add 1, to shift the indices from the end of a chain
73
- # to the start of a new chain
74
- chain_starts = np.where(res_id_decrement | chain_id_changes)[0] + 1
75
-
76
- # The first chain is not included yet -> Insert '[0]'
77
- if add_exclusive_stop:
78
- return np.concatenate(([0], chain_starts, [array.array_length()]))
79
- else:
80
- return np.concatenate(([0], chain_starts))
66
+ categories = ["chain_id"] + list(extra_categories)
67
+ if "sym_id" in array.get_annotation_categories():
68
+ categories.append("sym_id")
69
+ return get_segment_starts(
70
+ array,
71
+ add_exclusive_stop,
72
+ continuous_categories=("res_id",),
73
+ equal_categories=categories,
74
+ )
81
75
 
82
76
 
83
77
  def apply_chain_wise(array, data, function, axis=None):
@@ -449,6 +449,8 @@ def lddt(
449
449
  # Aggregate the fractions over the desired level
450
450
  if isinstance(aggregation, str) and aggregation == "all":
451
451
  # Average over all contacts
452
+ if len(fraction_preserved_bins) == 0:
453
+ return np.float32(np.nan)
452
454
  return np.mean(fraction_preserved_bins, axis=-1)
453
455
  else:
454
456
  # A string is also a 'Sequence'
@@ -33,10 +33,10 @@ def dot_bracket_from_structure(
33
33
  ----------
34
34
  nucleic_acid_strand : AtomArray
35
35
  The nucleic acid strand to be represented in DBL-notation.
36
- scores : ndarray, dtype=int, shape=(n,) (default: None)
36
+ scores : ndarray, dtype=int, shape=(n,)
37
37
  The score for each base pair, which is passed on to
38
38
  :func:`pseudoknots()`.
39
- max_pseudoknot_order : int (default: None)
39
+ max_pseudoknot_order : int
40
40
  The maximum pseudoknot order to be found. If a base pair would
41
41
  be of a higher order, it is represented as unpaired. If ``None``
42
42
  is given, all base pairs are evaluated.
@@ -82,9 +82,9 @@ def dot_bracket(basepairs, length, scores=None, max_pseudoknot_order=None):
82
82
  strand.
83
83
  length : int
84
84
  The number of bases in the strand.
85
- scores : ndarray, dtype=int, shape=(n,) (default: None)
85
+ scores : ndarray, dtype=int, shape=(n,)
86
86
  The score for each base pair, which is passed on to :func:`pseudoknots()`.
87
- max_pseudoknot_order : int (default: None)
87
+ max_pseudoknot_order : int
88
88
  The maximum pseudoknot order to be found. If a base pair would
89
89
  be of a higher order, it is represented as unpaired. If ``None``
90
90
  is given, all pseudoknot orders are evaluated.
@@ -57,59 +57,62 @@ def plot_nucleotide_secondary_structure(
57
57
  sequence. The positions are counted from zero.
58
58
  length : int
59
59
  The number of bases in the sequence.
60
- layout_type : RNAplotApp.Layout, optional (default: RNAplotApp.Layout.NAVIEW)
60
+ layout_type : RNAplotApp.Layout, optional
61
61
  The layout type according to the *RNAplot* documentation.
62
- draw_pseudoknots : bool, optional (default: True)
62
+ draw_pseudoknots : bool, optional
63
63
  Whether pseudoknotted bonds should be drawn.
64
- pseudoknot_order : iterable, optional (default: None)
64
+ pseudoknot_order : iterable, optional
65
65
  The pseudoknot order of each pair in the input `base_pairs`.
66
66
  If no pseudoknot order is given, a solution determined by
67
67
  :func:`biotite.structure.pseudoknots` is picked at random.
68
- angle : int or float, optional (default: 0)
68
+ angle : int or float, optional
69
69
  The angle the plot should be rotated.
70
- bond_linewidth : float or int or iterable, optional (default: 1)
70
+ bond_linewidth : float or int or iterable, optional
71
71
  The linewidth of each bond. Provide a single value to set the
72
72
  linewidth for all bonds or an iterable to set the linewidth for
73
73
  each individual bond.
74
- bond_linestyle : str or iterable, optional (default: None)
74
+ bond_linestyle : str or iterable, optional
75
75
  The *Matplotlib* compatible linestyle of each bond. Provide a
76
76
  single value to set the linewidth for all bonds or an iterable
77
77
  to set the linewidth for each individual bond. By default, solid
78
78
  lines are used for non-pseudoknotted bonds and dashed lines are
79
79
  used for pseudoknotted bonds.
80
- bond_color : str or ndarray, shape(n,) or shape(n,3) or shape(n,4), optional (default: 'black')
80
+ bond_color : str or ndarray, shape(n,) or shape(n,3) or shape(n,4), optional
81
81
  The *Matplotlib* compatible color of each bond. Provide a single
82
82
  string to set the color for all bonds or an array to set the
83
83
  color for each individual bond.
84
- backbone_linewidth : float, optional (default: 1)
84
+ backbone_linewidth : float, optional
85
85
  The linewidth of the backbone.
86
- backbone_linestyle : str, optional (default: 'solid')
86
+ backbone_linestyle : str, optional
87
87
  The *Matplotlib* compatible linestyle of the backbone.
88
- backbone_color : str or ndarray, shape=(3,) or shape=(4,), dtype=float, optional (default: 'grey')
88
+ backbone_color : str or ndarray, shape=(3,) or shape=(4,), dtype=float, optional
89
89
  The *Matplotlib* compatible color of the backbone.
90
- base_text : dict or iterable, optional (default: {'size': 'small'})
90
+ base_text : dict or iterable, optional
91
91
  The keyword parameters for the *Matplotlib* ``Text`` objects
92
92
  denoting the type of each base. Provide a single value to set
93
93
  the parameters for all labels or an iterable to set the
94
94
  parameters for each individual label.
95
- base_box : dict or iterable, optional (default: {'pad'=0, 'color'='white'})
95
+ The default is ``{'size': 'small'}``.
96
+ base_box : dict or iterable, optional)
96
97
  The *Matplotlib* compatible properties of the ``FancyBboxPatch``
97
98
  surrounding the base labels. Provide a single dictionary to
98
99
  set the properties of all base lables or an iterable to set the
99
100
  properties for each individual label.
100
- annotation_positions : iterable, optional (default: None)
101
+ The default is ``{'pad'=0, 'color'='white'}``.
102
+ annotation_positions : iterable, optional
101
103
  The positions of the bases to be numbered. By default every
102
104
  second base is annotated. Please note that while the positions
103
105
  in the sequence are counted from zero, they are displayed on the
104
106
  graph counted from one.
105
- annotation_offset : int or float, optional (default: 8.5)
107
+ annotation_offset : int or float, optional
106
108
  The offset of the annotations from the base labels.
107
- annotation_text : dict or iterable, optional (default: {'size': 'small'})
109
+ annotation_text : dict or iterable, optional
108
110
  The keyword parameters for the *Matplotlib* ``Text`` objects
109
111
  annotating the sequence. Provide a single value to set the
110
112
  parameters for all annotations or an iterable to set the
111
113
  parameters for each individual annotation.
112
- border : float, optional (default: 0.03)
114
+ The default is ``{'size': 'small'}``.
115
+ border : float, optional
113
116
  The percentage of the coordinate range to be left as whitespace
114
117
  to create a border around the plot.
115
118
  bin_path : str, optional
@@ -59,8 +59,7 @@ def hbond(
59
59
  The angle cutoff in degree between Donor-H..Acceptor to be
60
60
  considered a hydrogen bond.
61
61
  donor_elements, acceptor_elements : tuple of str
62
- Elements to be considered as possible donors or acceptors
63
- (Default: O, N, S).
62
+ Elements to be considered as possible donors or acceptors.
64
63
  periodic : bool, optional
65
64
  If true, hydrogen bonds can also be detected in periodic
66
65
  boundary conditions.
Binary file
@@ -15,9 +15,11 @@ __all__ = [
15
15
  "set_structure",
16
16
  "list_assemblies",
17
17
  "get_assembly",
18
- "get_symmetry_mates",
18
+ "get_unit_cell",
19
19
  ]
20
20
 
21
+ import warnings
22
+
21
23
 
22
24
  def get_model_count(pdb_file):
23
25
  """
@@ -232,6 +234,80 @@ def get_assembly(
232
234
  )
233
235
 
234
236
 
237
+ def get_unit_cell(
238
+ pdb_file, model=None, altloc="first", extra_fields=[], include_bonds=False
239
+ ):
240
+ """
241
+ Build a structure model containing all symmetric copies
242
+ of the structure within a single unit cell, given by the space
243
+ group.
244
+
245
+ This function receives the data from ``REMARK 290`` records in
246
+ the file.
247
+ Consequently, this remark must be present in the file, which is
248
+ usually only true for crystal structures.
249
+
250
+ Parameters
251
+ ----------
252
+ pdb_file : PDBFile
253
+ The file object.
254
+ model : int, optional
255
+ If this parameter is given, the function will return an
256
+ :class:`AtomArray` from the atoms corresponding to the given
257
+ model number (starting at 1).
258
+ Negative values are used to index models starting from the
259
+ last model instead of the first model.
260
+ If this parameter is omitted, an :class:`AtomArrayStack`
261
+ containing all models will be returned, even if the
262
+ structure contains only one model.
263
+ altloc : {'first', 'occupancy', 'all'}
264
+ This parameter defines how *altloc* IDs are handled:
265
+ - ``'first'`` - Use atoms that have the first
266
+ *altloc* ID appearing in a residue.
267
+ - ``'occupancy'`` - Use atoms that have the *altloc* ID
268
+ with the highest occupancy for a residue.
269
+ - ``'all'`` - Use all atoms.
270
+ Note that this leads to duplicate atoms.
271
+ When this option is chosen, the ``altloc_id``
272
+ annotation array is added to the returned structure.
273
+ extra_fields : list of str, optional
274
+ The strings in the list are optional annotation categories
275
+ that should be stored in the output array or stack.
276
+ These are valid values:
277
+ ``'atom_id'``, ``'b_factor'``, ``'occupancy'`` and
278
+ ``'charge'``.
279
+ include_bonds : bool, optional
280
+ If set to true, a :class:`BondList` will be created for the
281
+ resulting :class:`AtomArray` containing the bond information
282
+ from the file.
283
+ Bonds, whose order could not be determined from the
284
+ *Chemical Component Dictionary*
285
+ (e.g. especially inter-residue bonds),
286
+ have :attr:`BondType.ANY`, since the PDB format itself does
287
+ not support bond orders.
288
+
289
+ Returns
290
+ -------
291
+ symmetry_mates : AtomArray or AtomArrayStack
292
+ All atoms within a single unit cell.
293
+ The return type depends on the `model` parameter.
294
+
295
+ Notes
296
+ -----
297
+ To expand the structure beyond a single unit cell, use
298
+ :func:`repeat_box()` with the return value as its
299
+ input.
300
+
301
+ Examples
302
+ --------
303
+
304
+ >>> import os.path
305
+ >>> file = PDBFile.read(os.path.join(path_to_structures, "1aki.pdb"))
306
+ >>> atoms_in_unit_cell = get_unit_cell(file, model=1)
307
+ """
308
+ return pdb_file.get_unit_cell(model, altloc, extra_fields, include_bonds)
309
+
310
+
235
311
  def get_symmetry_mates(
236
312
  pdb_file, model=None, altloc="first", extra_fields=[], include_bonds=False
237
313
  ):
@@ -245,6 +321,8 @@ def get_symmetry_mates(
245
321
  Consequently, this remark must be present in the file, which is
246
322
  usually only true for crystal structures.
247
323
 
324
+ DEPRECATED: Use :func:`get_unit_cell()` instead.
325
+
248
326
  Parameters
249
327
  ----------
250
328
  pdb_file : PDBFile
@@ -303,4 +381,8 @@ def get_symmetry_mates(
303
381
  >>> file = PDBFile.read(os.path.join(path_to_structures, "1aki.pdb"))
304
382
  >>> atoms_in_unit_cell = get_symmetry_mates(file, model=1)
305
383
  """
306
- return pdb_file.get_symmetry_mates(model, altloc, extra_fields, include_bonds)
384
+ warnings.warn(
385
+ "'get_symmetry_mates()' is deprecated, use 'get_unit_cell()' instead",
386
+ DeprecationWarning,
387
+ )
388
+ return pdb_file.get_unit_cell(model, altloc, extra_fields, include_bonds)
@@ -936,7 +936,11 @@ class PDBFile(TextFile):
936
936
  if transform_start is None:
937
937
  raise InvalidFileError("No 'BIOMT' records found for chosen assembly")
938
938
  rotations, translations = _parse_transformations(
939
- assembly_lines[transform_start:stop]
939
+ [
940
+ line
941
+ for line in assembly_lines[transform_start:stop]
942
+ if len(line.strip()) > 0
943
+ ]
940
944
  )
941
945
  # Filter affected chains
942
946
  sub_structure = structure[
@@ -954,7 +958,7 @@ class PDBFile(TextFile):
954
958
 
955
959
  return assembly
956
960
 
957
- def get_symmetry_mates(
961
+ def get_unit_cell(
958
962
  self, model=None, altloc="first", extra_fields=[], include_bonds=False
959
963
  ):
960
964
  """
@@ -1021,7 +1025,7 @@ class PDBFile(TextFile):
1021
1025
 
1022
1026
  >>> import os.path
1023
1027
  >>> file = PDBFile.read(os.path.join(path_to_structures, "1aki.pdb"))
1024
- >>> atoms_in_unit_cell = file.get_symmetry_mates(model=1)
1028
+ >>> atoms_in_unit_cell = file.get_unit_cell(model=1)
1025
1029
  """
1026
1030
  # Get base structure
1027
1031
  structure = self.get_structure(
@@ -1041,6 +1045,83 @@ class PDBFile(TextFile):
1041
1045
  rotations, translations = _parse_transformations(transform_lines)
1042
1046
  return _apply_transformations(structure, rotations, translations)
1043
1047
 
1048
+ def get_symmetry_mates(
1049
+ self, model=None, altloc="first", extra_fields=[], include_bonds=False
1050
+ ):
1051
+ """
1052
+ Build a structure model containing all symmetric copies
1053
+ of the structure within a single unit cell, given by the space
1054
+ group.
1055
+
1056
+ This function receives the data from ``REMARK 290`` records in
1057
+ the file.
1058
+ Consequently, this remark must be present in the file, which is
1059
+ usually only true for crystal structures.
1060
+
1061
+ DEPRECATED: Use :meth:`get_unit_cell()` instead.
1062
+
1063
+ Parameters
1064
+ ----------
1065
+ model : int, optional
1066
+ If this parameter is given, the function will return an
1067
+ :class:`AtomArray` from the atoms corresponding to the given
1068
+ model number (starting at 1).
1069
+ Negative values are used to index models starting from the
1070
+ last model instead of the first model.
1071
+ If this parameter is omitted, an :class:`AtomArrayStack`
1072
+ containing all models will be returned, even if the
1073
+ structure contains only one model.
1074
+ altloc : {'first', 'occupancy', 'all'}
1075
+ This parameter defines how *altloc* IDs are handled:
1076
+ - ``'first'`` - Use atoms that have the first
1077
+ *altloc* ID appearing in a residue.
1078
+ - ``'occupancy'`` - Use atoms that have the *altloc* ID
1079
+ with the highest occupancy for a residue.
1080
+ - ``'all'`` - Use all atoms.
1081
+ Note that this leads to duplicate atoms.
1082
+ When this option is chosen, the ``altloc_id``
1083
+ annotation array is added to the returned structure.
1084
+ extra_fields : list of str, optional
1085
+ The strings in the list are optional annotation categories
1086
+ that should be stored in the output array or stack.
1087
+ These are valid values:
1088
+ ``'atom_id'``, ``'b_factor'``, ``'occupancy'`` and
1089
+ ``'charge'``.
1090
+ include_bonds : bool, optional
1091
+ If set to true, a :class:`BondList` will be created for the
1092
+ resulting :class:`AtomArray` containing the bond information
1093
+ from the file.
1094
+ Bonds, whose order could not be determined from the
1095
+ *Chemical Component Dictionary*
1096
+ (e.g. especially inter-residue bonds),
1097
+ have :attr:`BondType.ANY`, since the PDB format itself does
1098
+ not support bond orders.
1099
+
1100
+ Returns
1101
+ -------
1102
+ symmetry_mates : AtomArray or AtomArrayStack
1103
+ All atoms within a single unit cell.
1104
+ The return type depends on the `model` parameter.
1105
+
1106
+ Notes
1107
+ -----
1108
+ To expand the structure beyond a single unit cell, use
1109
+ :func:`repeat_box()` with the return value as its
1110
+ input.
1111
+
1112
+ Examples
1113
+ --------
1114
+
1115
+ >>> import os.path
1116
+ >>> file = PDBFile.read(os.path.join(path_to_structures, "1aki.pdb"))
1117
+ >>> atoms_in_unit_cell = file.get_symmetry_mates(model=1)
1118
+ """
1119
+ warnings.warn(
1120
+ "'get_symmetry_mates()' is deprecated, use 'get_unit_cell()' instead",
1121
+ DeprecationWarning,
1122
+ )
1123
+ return self.get_unit_cell(model, altloc, extra_fields, include_bonds)
1124
+
1044
1125
  def _index_models_and_atoms(self):
1045
1126
  # Line indices where a new model starts
1046
1127
  self._model_start_i = np.array(
@@ -1116,7 +1197,7 @@ class PDBFile(TextFile):
1116
1197
  conect_lines = [line for line in self.lines if line.startswith("CONECT")]
1117
1198
 
1118
1199
  # Mapping from atom ids to indices in an AtomArray
1119
- atom_id_to_index = np.zeros(atom_ids[-1] + 1, dtype=int)
1200
+ atom_id_to_index = np.full(atom_ids[-1] + 1, -1, dtype=int)
1120
1201
  try:
1121
1202
  for i, id in enumerate(atom_ids):
1122
1203
  atom_id_to_index[id] = i
@@ -1125,15 +1206,21 @@ class PDBFile(TextFile):
1125
1206
 
1126
1207
  bonds = []
1127
1208
  for line in conect_lines:
1128
- center_id = atom_id_to_index[decode_hybrid36(line[6:11])]
1209
+ center_index = atom_id_to_index[decode_hybrid36(line[6:11])]
1210
+ if center_index == -1:
1211
+ # Atom ID is not in the AtomArray (probably removed altloc)
1212
+ continue
1129
1213
  for i in range(11, 31, 5):
1130
1214
  id_string = line[i : i + 5]
1131
1215
  try:
1132
- id = atom_id_to_index[decode_hybrid36(id_string)]
1216
+ contact_index = atom_id_to_index[decode_hybrid36(id_string)]
1217
+ if contact_index == -1:
1218
+ # Atom ID is not in the AtomArray (probably removed altloc)
1219
+ continue
1133
1220
  except ValueError:
1134
1221
  # String is empty -> no further IDs
1135
1222
  break
1136
- bonds.append((center_id, id))
1223
+ bonds.append((center_index, contact_index))
1137
1224
 
1138
1225
  # The length of the 'atom_ids' array
1139
1226
  # is equal to the length of the AtomArray
@@ -511,7 +511,7 @@ class BinaryCIFBlock(_HierarchicalContainer):
511
511
 
512
512
  def __delitem__(self, key):
513
513
  try:
514
- return super().__setitem__("_" + key)
514
+ return super().__delitem__("_" + key)
515
515
  except KeyError:
516
516
  raise KeyError(key)
517
517
 
@@ -581,9 +581,12 @@ class BinaryCIFFile(File, _HierarchicalContainer):
581
581
 
582
582
  @property
583
583
  def block(self):
584
- if len(self) != 1:
584
+ if len(self) == 0:
585
+ raise ValueError("There are no blocks in the file")
586
+ elif len(self) > 1:
585
587
  raise ValueError("There are multiple blocks in the file")
586
- return self[next(iter(self))]
588
+ else:
589
+ return self[next(iter(self))]
587
590
 
588
591
  @staticmethod
589
592
  def subcomponent_class():
@@ -799,9 +799,12 @@ class CIFFile(_Component, File, MutableMapping):
799
799
 
800
800
  @property
801
801
  def block(self):
802
- if len(self) != 1:
802
+ if len(self) == 0:
803
+ raise ValueError("There are no blocks in the file")
804
+ elif len(self) > 1:
803
805
  raise ValueError("There are multiple blocks in the file")
804
- return self[next(iter(self))]
806
+ else:
807
+ return self[next(iter(self))]
805
808
 
806
809
  @staticmethod
807
810
  def subcomponent_class():