servalcat 0.4.100__cp311-cp311-win_amd64.whl → 0.4.105__cp311-cp311-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.

Potentially problematic release.


This version of servalcat might be problematic. Click here for more details.

servalcat/__init__.py CHANGED
@@ -6,5 +6,5 @@ This software is released under the
6
6
  Mozilla Public License, version 2.0; see LICENSE.
7
7
  """
8
8
 
9
- __version__ = '0.4.100'
10
- __date__ = '2025-01-22'
9
+ __version__ = '0.4.105'
10
+ __date__ = '2025-03-26'
Binary file
@@ -7,6 +7,7 @@ Mozilla Public License, version 2.0; see LICENSE.
7
7
  """
8
8
  from __future__ import absolute_import, division, print_function, generators
9
9
  import os
10
+ import time
10
11
  import re
11
12
  import gemmi
12
13
  import numpy
@@ -221,7 +222,16 @@ def write_stats_json_safe(stats, json_out):
221
222
  out_tmp = json_out + ".part"
222
223
  with open(out_tmp, "w") as ofs:
223
224
  json.dump(tmp, ofs, indent=2)
224
- os.replace(out_tmp, json_out)
225
+ for i in range(10):
226
+ try:
227
+ # On Windows, this fails when another process open the file
228
+ os.replace(out_tmp, json_out)
229
+ break
230
+ except PermissionError:
231
+ logger.writeln(f"{json_out} locked. retrying..")
232
+ time.sleep(0.5)
233
+ else:
234
+ raise RuntimeError(f"Cannot write {json_out}")
225
235
  logger.writeln(f"Refinement statistics saved: {json_out}")
226
236
  # write_stats_json_safe()
227
237
 
@@ -150,8 +150,8 @@ def main(args):
150
150
  topo = None
151
151
  if args.hydrogen == "all":
152
152
  logger.writeln("\nWARNING: in unrestrained refinement hydrogen atoms are not generated.\n")
153
- args.hydrogen = "yes"
154
- elif args.hydrogen == "no":
153
+ if args.hydrogen != "yes":
154
+ args.hydrogen = "no"
155
155
  st.remove_hydrogens()
156
156
  for i, cra in enumerate(st[0].all()):
157
157
  cra.atom.serial = i + 1
@@ -300,8 +300,7 @@ def main(args):
300
300
  if args.hklin:
301
301
  return
302
302
  # Calc Fo-Fc (and updated) maps
303
- diffmap_prefix = "{}_diffmap".format(args.output_prefix)
304
- calc_fofc(refiner.st, st_expanded, maps, monlib, ".mmcif", args, diffmap_prefix=diffmap_prefix)
303
+ calc_fofc(refiner.st, st_expanded, maps, monlib, ".mmcif", args, diffmap_prefix=args.output_prefix)
305
304
 
306
305
  # Final summary
307
306
  adpstats_txt = ""
@@ -320,8 +319,8 @@ def main(args):
320
319
  if args.mask_for_fofc:
321
320
  map_peaks_str = """\
322
321
  List Fo-Fc map peaks in the ASU:
323
- servalcat util map_peaks --map {diffmap_prefix}_normalized_fofc.mrc --model {prefix}.pdb --abs_level 4.0 \
324
- """.format(prefix=args.output_prefix, diffmap_prefix=diffmap_prefix)
322
+ servalcat util map_peaks --map {prefix}_normalized_fofc.mrc --model {prefix}.pdb --abs_level 4.0 \
323
+ """.format(prefix=args.output_prefix)
325
324
  else:
326
325
  map_peaks_str = "WARNING: --mask_for_fofc was not given, so the Fo-Fc map was not normalized."
327
326
 
@@ -343,7 +342,7 @@ Weight used: {final_weight:.3e}
343
342
  If you want to change the weight, give larger (looser restraints)
344
343
  or smaller (tighter) value to --weight=.
345
344
 
346
- Open refined model and {diffmap_prefix}.mtz with COOT:
345
+ Open refined model and {prefix}_maps.mtz with COOT:
347
346
  coot --script {prefix}_coot.py
348
347
 
349
348
  Open refined model, map and difference map with ChimeraX/ISOLDE:
@@ -358,7 +357,6 @@ chimerax {prefix}_chimerax.cxc
358
357
  adpstats=adpstats_txt.rstrip(),
359
358
  final_weight=args.weight,
360
359
  prefix=args.output_prefix,
361
- diffmap_prefix=diffmap_prefix,
362
360
  map_peaks_msg=map_peaks_str))
363
361
 
364
362
  # main()
@@ -24,11 +24,15 @@ b_to_u = utils.model.b_to_u
24
24
  def add_arguments(parser):
25
25
  parser.description = "program to refine crystallographic structures"
26
26
  parser.add_argument("--hklin", required=True)
27
+ parser.add_argument('--hklin_free',
28
+ help='Input MTZ file for test flags')
27
29
  parser.add_argument("-d", '--d_min', type=float)
28
30
  parser.add_argument('--d_max', type=float)
29
31
  parser.add_argument('--nbins', type=int,
30
32
  help="Number of bins (default: auto)")
31
33
  parser.add_argument("--labin", help="F,SIGF,FREE input")
34
+ parser.add_argument('--labin_free',
35
+ help='MTZ column of --hklin_free')
32
36
  parser.add_argument('--free', type=int,
33
37
  help='flag number for test set')
34
38
  parser.add_argument('--model', required=True,
@@ -128,7 +132,7 @@ def main(args):
128
132
  elif utils.fileio.is_mmhkl_file(hklin):
129
133
  hklin = utils.fileio.read_mmhkl(hklin)
130
134
  labin = decide_mtz_labels(hklin)
131
-
135
+ software_items = utils.fileio.software_items_from_mtz(hklin)
132
136
  try:
133
137
  hkldata, sts, fc_labs, centric_and_selections, args.free = process_input(hklin=hklin,
134
138
  labin=labin,
@@ -141,7 +145,10 @@ def main(args):
141
145
  use="work" if args.use_work_in_est else "test",
142
146
  max_bins=30,
143
147
  keep_charges=args.keep_charges,
144
- allow_unusual_occupancies=args.allow_unusual_occupancies)
148
+ allow_unusual_occupancies=args.allow_unusual_occupancies,
149
+ hklin_free=args.hklin_free,
150
+ labin_free=args.labin_free)
151
+
145
152
  except RuntimeError as e:
146
153
  raise SystemExit("Error: {}".format(e))
147
154
 
@@ -161,12 +168,13 @@ def main(args):
161
168
  if args.unrestrained:
162
169
  monlib = gemmi.MonLib()
163
170
  topo = None
164
- h_change = gemmi.HydrogenChange.NoChange
165
- if args.hydrogen == "all":
166
- logger.writeln("\nWARNING: in unrestrained refinement hydrogen atoms are not generated.\n")
167
- elif args.hydrogen == "no":
168
- st.remove_hydrogens()
171
+ if args.hydrogen == "yes":
172
+ h_change = gemmi.HydrogenChange.NoChange
173
+ else:
169
174
  h_change = gemmi.HydrogenChange.Remove
175
+ st.remove_hydrogens()
176
+ if args.hydrogen == "all":
177
+ logger.writeln("\nWARNING: in unrestrained refinement hydrogen atoms are not generated.\n")
170
178
  for i, cra in enumerate(st[0].all()):
171
179
  cra.atom.serial = i + 1
172
180
  else:
@@ -235,6 +243,7 @@ def main(args):
235
243
  weight_adjust_bond_rmsz_range=args.target_bond_rmsz_range,
236
244
  stats_json_out=args.output_prefix + "_stats.json")
237
245
  update_meta(st, stats[-1], ll)
246
+ st.meta.software = software_items + st.meta.software
238
247
  refiner.st.name = args.output_prefix
239
248
  utils.fileio.write_model(refiner.st, args.output_prefix, pdb=True, cif=True, hout=args.hout)
240
249
  if params["write_trajectory"]:
@@ -253,12 +262,12 @@ def main(args):
253
262
 
254
263
  # Write mtz file
255
264
  if ll.twin_data:
256
- labs = ["F_est", "F_exp", "FOM"]
265
+ labs = ["F_est", "F_exp"]
257
266
  elif is_int:
258
- labs = ["I", "SIGI", "FOM"]
267
+ labs = ["I", "SIGI", "F_est"]
259
268
  else:
260
- labs = ["FP", "SIGFP", "FOM"]
261
- labs.extend(["FWT", "DELFWT", "FC"])
269
+ labs = ["FP", "SIGFP"]
270
+ labs.extend(["FOM", "FWT", "DELFWT", "FC"])
262
271
  if "FAN" in hkldata.df:
263
272
  labs.append("FAN")
264
273
  if not args.no_solvent:
@@ -33,6 +33,8 @@ def add_arguments(parser):
33
33
  parser.add_argument('--keep_original_output', action='store_true', help="with .org extension")
34
34
  parser.add_argument("--keep_entities", action='store_true',
35
35
  help="Do not override entities")
36
+ parser.add_argument("--tls_addu", action='store_true',
37
+ help="Write prefix_addu.mmcif where TLS contribution is added to aniso U. Don't use this with 'tlso addu' keyword.")
36
38
  parser.add_argument('--prefix', help="output prefix")
37
39
  parser.add_argument("-v", "--version", action="version",
38
40
  version=logger.versions_str())
@@ -222,7 +224,7 @@ def get_output_model_names(xyzout):
222
224
  return pdb, mmcif
223
225
  # get_output_model_names()
224
226
 
225
- def modify_output(pdbout, cifout, fixes, hout, cispeps, keep_original_output=False):
227
+ def modify_output(pdbout, cifout, fixes, hout, cispeps, software_items, keep_original_output=False, tls_addu=False):
226
228
  st = utils.fileio.read_structure(cifout)
227
229
  st.cispeps = cispeps
228
230
  if os.path.exists(pdbout):
@@ -256,11 +258,37 @@ def modify_output(pdbout, cifout, fixes, hout, cispeps, keep_original_output=Fal
256
258
  # add servalcat version
257
259
  if len(st.meta.software) > 0 and st.meta.software[-1].name == "refmac":
258
260
  st.meta.software[-1].version += f" (refmacat {servalcat.__version__})"
261
+ st.meta.software = software_items + st.meta.software
259
262
 
260
263
  suffix = ".org"
261
264
  os.rename(cifout, cifout + suffix)
262
265
  utils.fileio.write_mmcif(st, cifout, cifout + suffix)
263
266
 
267
+ if tls_addu:
268
+ doc_ref = gemmi.cif.read(cifout + suffix)
269
+ tls_groups = {int(x.id): x for x in st.meta.refinement[0].tls_groups}
270
+ tls_details = doc_ref[0].find_value("_ccp4_refine_tls.details")
271
+ if tls_groups and gemmi.cif.as_string(tls_details) == "U values: residual only":
272
+ st2 = st.clone()
273
+ for cra in st2[0].all():
274
+ tlsgr = tls_groups.get(cra.atom.tls_group_id)
275
+ if cra.atom.tls_group_id > 0 and tlsgr is not None:
276
+ if not cra.atom.aniso.nonzero():
277
+ u = cra.atom.b_iso * utils.model.b_to_u
278
+ cra.atom.aniso = gemmi.SMat33f(u, u, u, 0, 0, 0)
279
+ u_from_tls = gemmi.calculate_u_from_tls(tlsgr, cra.atom.pos)
280
+ cra.atom.aniso += gemmi.SMat33f(*u_from_tls.elements_pdb())
281
+ cra.atom.b_iso = cra.atom.aniso.trace() / 3. * utils.model.u_to_b
282
+ cifout2 = cifout[:cifout.rindex(".")] + "_addu" + cifout[cifout.rindex("."):]
283
+ doc_ref[0].set_pair("_ccp4_refine_tls.details", gemmi.cif.quote("U values: with tls added"))
284
+ utils.fileio.write_mmcif(st2, cifout2, cif_ref_doc=doc_ref)
285
+ else:
286
+ if not tls_groups:
287
+ msg = "TLS group definition not found in the model"
288
+ else:
289
+ msg = "TLS already applied to the U values"
290
+ logger.writeln(f"Error: --tls_addu requested, but {msg}.")
291
+
264
292
  if st.has_d_fraction:
265
293
  st.store_deuterium_as_fraction(False) # also useful for pdb
266
294
  logger.writeln("will write a H/D expanded mmcif file")
@@ -313,6 +341,11 @@ def main(args):
313
341
  # TODO what if restin is given or make cr prepared is given?
314
342
  # TODO check make pept/link/suga/ss/conn/symm/chain
315
343
 
344
+ if "hklin" in opts: # for history
345
+ software_items = utils.fileio.software_items_from_mtz(opts["hklin"])
346
+ else:
347
+ software_items = []
348
+
316
349
  # Process model
317
350
  crdout = None
318
351
  refmac_fixes = None
@@ -380,7 +413,8 @@ def main(args):
380
413
  if xyzin is not None:
381
414
  pdbout, cifout = get_output_model_names(opts.get("xyzout"))
382
415
  if os.path.exists(cifout):
383
- modify_output(pdbout, cifout, refmac_fixes, keywords["make"].get("hout"), cispeps, args.keep_original_output)
416
+ modify_output(pdbout, cifout, refmac_fixes, keywords["make"].get("hout"), cispeps,
417
+ software_items, args.keep_original_output, args.tls_addu)
384
418
  # main()
385
419
 
386
420
  def command_line():
servalcat/spa/fofc.py CHANGED
@@ -357,7 +357,7 @@ def write_files(hkldata, map_labs, grid_start, stats_str,
357
357
  hkldata2.translate(lab, -shifts)
358
358
  hkldata = hkldata2
359
359
 
360
- dump_to_mtz(hkldata, map_labs, "{}.mtz".format(output_prefix))
360
+ dump_to_mtz(hkldata, map_labs, "{}_maps.mtz".format(output_prefix))
361
361
  if stats_str: open("{}_Fstats.log".format(output_prefix), "w").write(stats_str)
362
362
  # write_files()
363
363
 
@@ -458,15 +458,15 @@ def main(args):
458
458
 
459
459
  py_out = "{}_coot.py".format(args.output_prefix)
460
460
  write_coot_script(py_out, model_file=args.model,
461
- mtz_file=args.output_prefix+".mtz",
461
+ mtz_file=args.output_prefix+"_maps.mtz",
462
462
  contour_fo=None if mask is None else 1.2,
463
463
  contour_fofc=None if mask is None else 3.0,
464
464
  ncs_ops=ncs_org)
465
- logger.writeln("\nOpen model and diffmap.mtz with COOT:")
465
+ logger.writeln("\nOpen model and diffmap mtz with COOT:")
466
466
  logger.writeln("coot --script " + py_out)
467
467
  if mask is not None:
468
468
  logger.writeln("\nWant to list Fo-Fc map peaks? Try:")
469
- if omit_h_electron:
469
+ if args.omit_h_electron:
470
470
  logger.writeln("servalcat util map_peaks --map {}_normalized_fofc_flipsign.mrc --model {} --abs_level 4.0".format(args.output_prefix, args.model))
471
471
  else:
472
472
  logger.writeln("servalcat util map_peaks --map {}_normalized_fofc.mrc --model {} --abs_level 4.0".format(args.output_prefix, args.model))
@@ -125,7 +125,7 @@ def add_arguments(parser):
125
125
  group = parser.add_mutually_exclusive_group()
126
126
  group.add_argument('--mask_for_fofc', help="Mask file for Fo-Fc map calculation")
127
127
  group.add_argument('--mask_radius_for_fofc', type=float, help="Mask radius for Fo-Fc map calculation")
128
- parser.add_argument('--trim_fofc_mtz', action="store_true", help="diffmap.mtz will have smaller cell (if --mask_for_fofc is given)")
128
+ parser.add_argument('--trim_fofc_mtz', action="store_true", help="maps mtz will have smaller cell (if --mask_for_fofc is given)")
129
129
  parser.add_argument("--fsc_resolution", type=float,
130
130
  help="High resolution limit for FSC calculation. Default: Nyquist")
131
131
 
@@ -312,7 +312,7 @@ def calc_fofc(st, st_expanded, maps, monlib, model_format, args, diffmap_prefix=
312
312
  # Create Coot script
313
313
  spa.fofc.write_coot_script("{}_coot.py".format(args.output_prefix),
314
314
  model_file="{}.pdb".format(args.output_prefix), # as Coot is not good at mmcif file..
315
- mtz_file="{}.mtz".format(diffmap_prefix),
315
+ mtz_file="{}_maps.mtz".format(diffmap_prefix),
316
316
  contour_fo=None if mask is None else 1.2,
317
317
  contour_fofc=None if mask is None else 3.0,
318
318
  ncs_ops=st.ncs)
@@ -364,7 +364,7 @@ Weight used: {final_weight}
364
364
  If you want to change the weight, give larger (looser restraints)
365
365
  or smaller (tighter) value to --weight_auto_scale=.
366
366
 
367
- Open refined model and diffmap.mtz with COOT:
367
+ Open refined model and maps mtz with COOT:
368
368
  coot --script {prefix}_coot.py
369
369
 
370
370
  Open refined model, map and difference map with ChimeraX/ISOLDE:
@@ -330,7 +330,7 @@ def symmodel(args):
330
330
  map_and_start = None
331
331
  if args.map:
332
332
  logger.writeln("Reading cell from map")
333
- map_and_start = fileio.read_ccp4_map(args.map)
333
+ map_and_start = fileio.read_ccp4_map(args.map, header_only=True)
334
334
  st.cell = map_and_start[0].unit_cell
335
335
  elif args.cell:
336
336
  st.cell = gemmi.UnitCell(*args.cell)
@@ -388,7 +388,7 @@ def helical_biomt(args):
388
388
  map_and_start = None
389
389
  if args.map:
390
390
  logger.writeln("Reading cell from map")
391
- map_and_start = fileio.read_ccp4_map(args.map)
391
+ map_and_start = fileio.read_ccp4_map(args.map, header_only=True)
392
392
  st.cell = map_and_start[0].unit_cell
393
393
  elif args.cell:
394
394
  st.cell = gemmi.UnitCell(*args.cell)
@@ -1307,7 +1307,7 @@ def mask_from_model(args):
1307
1307
  st = fileio.read_structure(args.model) # TODO option to (or not to) expand NCS
1308
1308
  if args.selection:
1309
1309
  gemmi.Selection(args.selection).remove_not_selected(st)
1310
- gr, grid_start, _ = fileio.read_ccp4_map(args.map)
1310
+ gr, grid_start, _ = fileio.read_ccp4_map(args.map, header_only=True)
1311
1311
  mask = maps.mask_from_model(st, args.radius, soft_edge=args.soft_edge, grid=gr)
1312
1312
  maps.write_ccp4_map(args.output, mask, grid_start=grid_start)
1313
1313
  # mask_from_model()
@@ -1367,7 +1367,7 @@ def map2mtz(args):
1367
1367
 
1368
1368
  def sm2mm(args):
1369
1369
  if args.output_prefix is None:
1370
- args.output_prefix = fileio.splitext(args.files[0])[0]
1370
+ args.output_prefix = os.path.basename(fileio.splitext(args.files[0])[0])
1371
1371
  st, mtz = fileio.read_small_molecule_files(args.files)
1372
1372
  if st is not None:
1373
1373
  fileio.write_model(st, prefix=args.output_prefix, pdb=True, cif=True)
servalcat/utils/fileio.py CHANGED
@@ -18,6 +18,7 @@ import subprocess
18
18
  import gemmi
19
19
  import numpy
20
20
  import gzip
21
+ import traceback
21
22
 
22
23
  def splitext(path):
23
24
  if path.endswith((".bz2",".gz")):
@@ -66,14 +67,15 @@ def check_model_format(xyzin):
66
67
  return ".pdb"
67
68
  # check_model_format()
68
69
 
69
- def write_mmcif(st, cif_out, cif_ref=None):
70
+ def write_mmcif(st, cif_out, cif_ref=None, cif_ref_doc=None):
70
71
  """
71
72
  Refmac fails if _entry.id is longer than 80 chars including quotations
72
73
  """
73
74
  st_new = st.clone()
74
75
  logger.writeln("Writing mmCIF file: {}".format(cif_out))
75
- if cif_ref:
76
- logger.writeln(" using mmCIF metadata from: {}".format(cif_ref))
76
+ if cif_ref or cif_ref_doc:
77
+ if cif_ref:
78
+ logger.writeln(" using mmCIF metadata from: {}".format(cif_ref))
77
79
  groups = gemmi.MmcifOutputGroups(False)
78
80
  groups.group_pdb = True
79
81
  groups.ncs = True
@@ -88,18 +90,19 @@ def write_mmcif(st, cif_out, cif_ref=None):
88
90
  groups.conn = True
89
91
  groups.software = True
90
92
  groups.auth_all = True
91
- # FIXME is this all?
92
- try:
93
- doc = read_cif_safe(cif_ref)
94
- except Exception as e:
95
- # Sometimes refmac writes a broken mmcif file..
96
- logger.error("Error in mmCIF reading: {}".format(e))
97
- logger.error(" Give up using cif reference.")
98
- return write_mmcif(st, cif_out)
93
+ # FIXME is this all?
94
+ if cif_ref:
95
+ try:
96
+ cif_ref_doc = read_cif_safe(cif_ref)
97
+ except Exception as e:
98
+ # Sometimes refmac writes a broken mmcif file..
99
+ logger.error("Error in mmCIF reading: {}".format(e))
100
+ logger.error(" Give up using cif reference.")
101
+ return write_mmcif(st, cif_out)
99
102
 
100
- blocks = list(filter(lambda b: b.find_loop("_atom_site.id"), doc))
103
+ blocks = list(filter(lambda b: b.find_loop("_atom_site.id"), cif_ref_doc))
101
104
  if len(blocks) == 0:
102
- logger.writeln("No _atom_site found in {}".format(cif_ref))
105
+ logger.writeln("No _atom_site found in reference")
103
106
  logger.writeln(" Give up using cif reference.")
104
107
  return write_mmcif(st, cif_out)
105
108
  block = blocks[0]
@@ -108,7 +111,7 @@ def write_mmcif(st, cif_out, cif_ref=None):
108
111
  block.find_mmcif_category("_atom_sites.").erase()
109
112
  st_new.update_mmcif_block(block, groups)
110
113
  if "_entry.id" in st_new.info: st_new.info["_entry.id"] = st_new.info["_entry.id"][:78]
111
- doc.write_file(cif_out, options=gemmi.cif.Style.Aligned)
114
+ cif_ref_doc.write_file(cif_out, options=gemmi.cif.Style.Aligned)
112
115
  else:
113
116
  st_new.name = st_new.name[:78] # this will become _entry.id
114
117
  if "_entry.id" in st_new.info: st_new.info["_entry.id"] = st_new.info["_entry.id"][:78]
@@ -162,17 +165,20 @@ def read_shifts_txt(shifts_txt):
162
165
  return ret
163
166
  # read_shifts_txt()
164
167
 
165
- def read_ccp4_map(filename, setup=True, default_value=0., pixel_size=None, ignore_origin=True):
166
- m = gemmi.read_ccp4_map(filename)
167
- g = m.grid
168
+ def read_ccp4_map(filename, header_only=False, setup=True, default_value=0., pixel_size=None, ignore_origin=True):
169
+ if header_only:
170
+ m = gemmi.read_ccp4_header(filename)
171
+ else:
172
+ m = gemmi.read_ccp4_map(filename)
168
173
  grid_cell = [m.header_i32(x) for x in (8,9,10)]
169
174
  grid_start = [m.header_i32(x) for x in (5,6,7)]
170
175
  grid_shape = [m.header_i32(x) for x in (1,2,3)]
171
176
  axis_pos = m.axis_positions()
172
177
  axis_letters = ["","",""]
173
178
  for i, l in zip(axis_pos, "XYZ"): axis_letters[i] = l
174
- spacings = [1./g.unit_cell.reciprocal().parameters[i]/grid_cell[i] for i in (0,1,2)]
175
- voxel_size = [g.unit_cell.parameters[i]/grid_cell[i] for i in (0,1,2)]
179
+ cell = gemmi.UnitCell(*(m.header_float(x) for x in range(11,17)))
180
+ spacings = [1./cell.reciprocal().parameters[i]/grid_cell[i] for i in (0,1,2)]
181
+ voxel_size = [cell.parameters[i]/grid_cell[i] for i in (0,1,2)]
176
182
  origin = [m.header_float(x) for x in (50,51,52)]
177
183
  label = m.header_str(57, 80)
178
184
  label = label[:label.find("\0")]
@@ -181,7 +187,7 @@ def read_ccp4_map(filename, setup=True, default_value=0., pixel_size=None, ignor
181
187
  logger.writeln(" Map mode: {}".format(m.header_i32(4)))
182
188
  logger.writeln(" Start: {:4d} {:4d} {:4d}".format(*grid_start))
183
189
  logger.writeln(" Shape: {:4d} {:4d} {:4d}".format(*grid_shape))
184
- logger.writeln(" Cell: {} {} {} {} {} {}".format(*g.unit_cell.parameters))
190
+ logger.writeln(" Cell: {} {} {} {} {} {}".format(*cell.parameters))
185
191
  logger.writeln(" Axis order: {}".format(" ".join(axis_letters)))
186
192
  logger.writeln(" Space group: {}".format(m.header_i32(23)))
187
193
  logger.writeln(" Spacing: {:.6f} {:.6f} {:.6f}".format(*spacings))
@@ -195,9 +201,17 @@ def read_ccp4_map(filename, setup=True, default_value=0., pixel_size=None, ignor
195
201
  logger.writeln(" Label: {}".format(label))
196
202
  logger.writeln("")
197
203
 
204
+ if header_only:
205
+ grid = gemmi.FloatGrid(*grid_cell if setup else grid_shape) # waste of memory, but unavoidable for now
206
+ grid.set_unit_cell(cell)
207
+ grid.spacegroup = gemmi.find_spacegroup_by_number(m.header_i32(23))
208
+ else:
209
+ grid = m.grid
210
+
198
211
  if setup:
199
- if default_value is None: default_value = float("nan")
200
- m.setup(default_value)
212
+ if not header_only:
213
+ if default_value is None: default_value = float("nan")
214
+ m.setup(default_value)
201
215
  grid_start = [grid_start[i] for i in axis_pos]
202
216
 
203
217
  if pixel_size is not None:
@@ -207,13 +221,13 @@ def read_ccp4_map(filename, setup=True, default_value=0., pixel_size=None, ignor
207
221
  pixel_size = [pixel_size, pixel_size, pixel_size]
208
222
 
209
223
  logger.writeln("Overriding pixel size with {:.6f} {:.6f} {:.6f}".format(*pixel_size))
210
- orgc = m.grid.unit_cell.parameters
224
+ orgc = grid.unit_cell.parameters
211
225
  new_abc = [orgc[i]*pixel_size[i]/voxel_size[i] for i in (0,1,2)]
212
- m.grid.unit_cell = gemmi.UnitCell(new_abc[0], new_abc[1], new_abc[2],
213
- orgc[3], orgc[4], orgc[5])
214
- logger.writeln(" New cell= {:.1f} {:.1f} {:.1f} {:.1f} {:.1f} {:.1f}".format(*m.grid.unit_cell.parameters))
226
+ new_cell = gemmi.UnitCell(new_abc[0], new_abc[1], new_abc[2], orgc[3], orgc[4], orgc[5])
227
+ grid.set_unit_cell(new_cell)
228
+ logger.writeln(" New cell= {:.1f} {:.1f} {:.1f} {:.1f} {:.1f} {:.1f}".format(*grid.unit_cell.parameters))
215
229
 
216
- return [m.grid, grid_start, grid_shape]
230
+ return [grid, grid_start, grid_shape]
217
231
  # read_ccp4_map()
218
232
 
219
233
  def read_halfmaps(files, pixel_size=None, fail=True):
@@ -267,6 +281,19 @@ def is_mmhkl_file(hklin):
267
281
  # otherwise cannot decide
268
282
  # is_smhkl()
269
283
 
284
+ def software_items_from_mtz(hklin):
285
+ try:
286
+ if type(hklin) is gemmi.Mtz:
287
+ mtz = hklin
288
+ else:
289
+ mtz = gemmi.read_mtz_file(hklin, with_data=False)
290
+ return gemmi.get_software_from_mtz_history(mtz.history)
291
+ except:
292
+ logger.writeln(f"Failed to read software info from {hklin}")
293
+ logger.writeln(traceback.format_exc())
294
+ return []
295
+ # software_items_from_mtz()
296
+
270
297
  def read_map_from_mtz(mtz_in, cols, grid_size=None, sample_rate=3):
271
298
  mtz = read_mmhkl(mtz_in)
272
299
  d_min = mtz.resolution_high() # TODO get resolution for column?
@@ -551,9 +578,16 @@ def read_shelx_ins(ins_in=None, lines_in=None, ignore_q_peaks=True): # TODO supp
551
578
  if len(sp) > 11:
552
579
  u = list(map(float, sp[6:12]))
553
580
  site.aniso = gemmi.SMat33d(u[0], u[1], u[2], u[5], u[4], u[3])
554
- #TODO site.u_iso needs to be set?
581
+ site.u_iso = sum(u[:3]) / 3.
555
582
  else:
556
583
  site.u_iso = float(sp[6])
584
+ if site.u_iso < 0:
585
+ # most recently defined non-hydrogen atom
586
+ u_p = next((s.u_iso for s in reversed(ss.sites) if not s.element.is_hydrogen), None)
587
+ if u_p is not None:
588
+ site.u_iso *= -u_p
589
+ else:
590
+ logger.writeln(f"WARNING: parent atom not found for {site.label}")
557
591
 
558
592
  ss.add_site(site)
559
593
 
@@ -693,20 +727,21 @@ def read_small_molecule_files(files):
693
727
  # first pass - find structure
694
728
  for filename in files:
695
729
  ext = splitext(filename)[1]
696
- if ext in (".cif", ".res", ".ins"):
730
+ if ext in (".cif", ".res", ".ins", ".pdb", ".ent", ".mmcif"):
697
731
  try:
698
732
  st = read_structure(filename)
699
733
  except:
700
734
  continue
701
735
  logger.writeln("Coordinates read from: {}".format(filename))
702
- if ext == ".cif":
703
- b = gemmi.cif.read(filename).sole_block()
704
- res_str = b.find_value("_shelx_res_file")
705
- else:
706
- res_str = open(filename).read()
707
- if res_str:
708
- _, info = read_shelx_ins(lines_in=res_str.splitlines())
709
- hklf = info["hklf"]
736
+ if ext in (".cif", ".res", ".ins"):
737
+ if ext == ".cif":
738
+ b = gemmi.cif.read(filename).sole_block()
739
+ res_str = b.find_value("_shelx_res_file")
740
+ else:
741
+ res_str = open(filename).read()
742
+ if res_str:
743
+ _, info = read_shelx_ins(lines_in=res_str.splitlines())
744
+ hklf = info["hklf"]
710
745
  if st is None:
711
746
  logger.writeln("ERROR: coordinates not found.")
712
747
  return None, None
@@ -733,12 +768,16 @@ def read_sequence_file(f):
733
768
  # TODO needs improvement
734
769
  # return a list of [name, sequence]
735
770
  ret = []
736
- for l in open(f):
771
+ for i, l in enumerate(open(f)):
737
772
  l = l.strip()
738
773
  if l.startswith(">"):
739
774
  name = l[1:].strip()
740
775
  ret.append([name, ""])
741
776
  elif l:
742
777
  if not ret: ret.append(["", ""])
743
- ret[-1][1] += l.replace("*", "").replace("-", "").upper()
778
+ tmp = l.replace("*", "").replace("-", "").upper()
779
+ r = re.search("[^A-Z]", tmp)
780
+ if r:
781
+ raise RuntimeError(f"Invalid character in the sequence file: {f}:{i+1}")
782
+ ret[-1][1] += tmp
744
783
  return ret
servalcat/utils/model.py CHANGED
@@ -494,9 +494,10 @@ def filter_contacting_ncs(st, cutoff=5.):
494
494
  st.setup_cell_images()
495
495
  ns = gemmi.NeighborSearch(st[0], st.cell, cutoff*2).populate() # This is considered crystallographic cell if not 1 1 1. Undesirable result may be seen.
496
496
  cs = gemmi.ContactSearch(cutoff)
497
+ cs.twice = True # since we need all image_idx
497
498
  cs.ignore = gemmi.ContactSearch.Ignore.SameAsu
498
499
  results = cs.find_contacts(ns)
499
- indices = set([r.image_idx for r in results])
500
+ indices = {r.image_idx for r in results}
500
501
  logger.writeln(" contacting copies: {}".format(indices))
501
502
  ops = [st.ncs[i-1] for i in indices] # XXX is this correct? maybe yes as long as identity operator is not there
502
503
  st.ncs.clear()
@@ -39,8 +39,11 @@ def update_ncs_from_args(args, st, map_and_start=None, filter_contacting=False,
39
39
  ncsops = ncsops_from_args(args, st.cell, map_and_start=map_and_start, st=st,
40
40
  helical_min_n=helical_min_n, helical_max_n=helical_max_n)
41
41
 
42
- st.ncs.clear()
43
- st.ncs.extend([x for x in ncsops if not x.tr.is_identity()])
42
+ st.ncs = [x for x in ncsops if not x.tr.is_identity()]
43
+ # To write identity op to the output model
44
+ idop_id = next((x.id for x in ncsops if x.tr.is_identity()), None)
45
+ if idop_id:
46
+ st.info["_struct_ncs_oper.id"] = idop_id
44
47
 
45
48
  if filter_contacting:
46
49
  model.filter_contacting_ncs(st)
servalcat/xtal/sigmaa.py CHANGED
@@ -30,10 +30,14 @@ def add_arguments(parser):
30
30
  parser.description = 'Sigma-A parameter estimation for crystallographic data'
31
31
  parser.add_argument('--hklin', required=True,
32
32
  help='Input MTZ file')
33
+ parser.add_argument('--hklin_free',
34
+ help='Input MTZ file for test flags')
33
35
  parser.add_argument('--spacegroup',
34
36
  help='Override space group')
35
37
  parser.add_argument('--labin',
36
- help='MTZ column for F,SIGF,FREE')
38
+ help='MTZ columns of --hklin for F,SIGF,FREE')
39
+ parser.add_argument('--labin_free',
40
+ help='MTZ column of --hklin_free')
37
41
  parser.add_argument('--free', type=int,
38
42
  help='flag number for test set')
39
43
  parser.add_argument('--model', required=True, nargs="+", action="append",
@@ -97,6 +101,8 @@ def calc_r_and_cc(hkldata, centric_and_selections, twin_data=None):
97
101
  else:
98
102
  obs = obs_sqrt = hkldata.df.FP
99
103
  calc = calc_sqrt = Fc
104
+ if "CC*" in stats: # swap the positions
105
+ stats.insert(len(stats.columns)-1, "CC*", stats.pop("CC*"))
100
106
  if has_free:
101
107
  for lab in (cclab, rlab):
102
108
  for suf in ("work", "free"):
@@ -1037,6 +1043,7 @@ def calculate_maps_int(hkldata, b_aniso, fc_labs, D_labs, centric_and_selections
1037
1043
  nmodels = len(fc_labs)
1038
1044
  hkldata.df["FWT"] = 0j * numpy.nan
1039
1045
  hkldata.df["DELFWT"] = 0j * numpy.nan
1046
+ hkldata.df["F_est"] = numpy.nan
1040
1047
  hkldata.df["FOM"] = numpy.nan # FOM proxy, |<F>| / <|F|>
1041
1048
  has_ano = "I(+)" in hkldata.df and "I(-)" in hkldata.df
1042
1049
  if has_ano:
@@ -1059,6 +1066,7 @@ def calculate_maps_int(hkldata, b_aniso, fc_labs, D_labs, centric_and_selections
1059
1066
  hkldata.df.loc[cidxes, "FWT"] = 2 * f * exp_ip - DFc[cidxes]
1060
1067
  hkldata.df.loc[cidxes, "DELFWT"] = f * exp_ip - DFc[cidxes]
1061
1068
  hkldata.df.loc[cidxes, "FOM"] = m_proxy
1069
+ hkldata.df.loc[cidxes, "F_est"] = f
1062
1070
  if has_ano:
1063
1071
  f_p, _ = expected_F_from_int(ano_data[cidxes,0], ano_data[cidxes,1],
1064
1072
  k_ani[cidxes], DFc[cidxes], eps[cidxes], c, S)
@@ -1159,9 +1167,32 @@ def decide_mtz_labels(mtz, find_free=True, require=None):
1159
1167
  return labin
1160
1168
  # decide_mtz_labels()
1161
1169
 
1170
+ def decide_spacegroup(sg_user, sg_st, sg_hkl):
1171
+ assert sg_hkl is not None
1172
+ ret = None
1173
+ if sg_user is not None:
1174
+ ret = sg_user
1175
+ logger.writeln(f"Space group overridden by user. Using {ret.xhm()}")
1176
+ else:
1177
+ ret = sg_hkl
1178
+ if sg_hkl != sg_st:
1179
+ if sg_st and sg_st.laue_str() != sg_hkl.laue_str():
1180
+ raise RuntimeError("Crystal symmetry mismatch between model and data")
1181
+ logger.writeln("Warning: space group mismatch between model and mtz")
1182
+ if sg_st and sg_st.laue_str() == sg_hkl.laue_str():
1183
+ logger.writeln(" using space group from model")
1184
+ ret = sg_st
1185
+ else:
1186
+ logger.writeln(" using space group from mtz")
1187
+ logger.writeln("")
1188
+
1189
+ return ret
1190
+ # decide_spacegroup
1191
+
1162
1192
  def process_input(hklin, labin, n_bins, free, xyzins, source, d_max=None, d_min=None,
1163
1193
  n_per_bin=None, use="all", max_bins=None, cif_index=0, keep_charges=False,
1164
- allow_unusual_occupancies=False, space_group=None):
1194
+ allow_unusual_occupancies=False, space_group=None,
1195
+ hklin_free=None, labin_free=None):
1165
1196
  if labin: assert 1 < len(labin) < 6
1166
1197
  assert use in ("all", "work", "test")
1167
1198
 
@@ -1181,13 +1212,18 @@ def process_input(hklin, labin, n_bins, free, xyzins, source, d_max=None, d_min=
1181
1212
  assert len(xyzins) == 1
1182
1213
  assert not sts
1183
1214
  st, mtz = utils.fileio.read_small_molecule_files([hklin, xyzins[0]])
1215
+ if None in (st, mtz):
1216
+ raise SystemExit("Failed to read small molecule file(s)")
1184
1217
  sts = [st]
1185
1218
 
1186
1219
  for st in sts:
1187
1220
  utils.model.check_occupancies(st, raise_error=not allow_unusual_occupancies)
1188
1221
 
1222
+ sg_use = decide_spacegroup(sg_user=gemmi.SpaceGroup(space_group) if space_group else None,
1223
+ sg_st=sts[0].find_spacegroup() if sts else None,
1224
+ sg_hkl=mtz.spacegroup)
1189
1225
  if not labin:
1190
- labin = decide_mtz_labels(mtz)
1226
+ labin = decide_mtz_labels(mtz, find_free=hklin_free is None)
1191
1227
  col_types = {x.label:x.type for x in mtz.columns}
1192
1228
  if labin[0] not in col_types:
1193
1229
  raise RuntimeError("MTZ column not found: {}".format(labin[0]))
@@ -1197,10 +1233,30 @@ def process_input(hklin, labin, n_bins, free, xyzins, source, d_max=None, d_min=
1197
1233
  "K": ("anomalous intensity", ["I(+)","SIGI(+)", "I(-)", "SIGI(-)"], ["K", "M", "K", "M"])}
1198
1234
  if col_types[labin[0]] not in labs_and_types:
1199
1235
  raise RuntimeError("MTZ column {} is neither amplitude nor intensity".format(labin[0]))
1236
+ if col_types[labin[0]] == "J": # may be unmerged data
1237
+ ints = gemmi.Intensities()
1238
+ ints.set_data(mtz.cell, sg_use, mtz.make_miller_array(),
1239
+ mtz.array[:,mtz.column_labels().index(labin[0])],
1240
+ mtz.array[:,mtz.column_labels().index(labin[1])])
1241
+ dtype = ints.prepare_for_merging(gemmi.DataType.Mean) # do we want Anomalous?
1242
+ ints_bak = ints.clone() # for stats
1243
+ ints.merge_in_place(dtype)
1244
+ if (ints.nobs_array > 1).any():
1245
+ mtz = ints.prepare_merged_mtz(with_nobs=False)
1246
+ labin = mtz.column_labels()[3:]
1247
+ col_types = {x.label:x.type for x in mtz.columns}
1248
+ mult = ints.nobs_array.mean()
1249
+ logger.writeln(f"Input data were merged (multiplicity: {mult:.2f}). Overriding labin={','.join(labin)}")
1250
+ else:
1251
+ ints_bak = None
1252
+ else:
1253
+ ints_bak = None
1254
+
1200
1255
  name, newlabels, require_types = labs_and_types[col_types[labin[0]]]
1201
1256
  logger.writeln("Observation type: {}".format(name))
1202
1257
  if len(newlabels) < len(labin): newlabels.append("FREE")
1203
1258
  hkldata = utils.hkl.hkldata_from_mtz(mtz, labin, newlabels=newlabels, require_types=require_types)
1259
+ hkldata.sg = sg_use
1204
1260
  hkldata.mask_invalid_obs_values(newlabels)
1205
1261
  if newlabels[0] == "F(+)":
1206
1262
  hkldata.merge_anomalous(newlabels[:4], ["FP", "SIGFP"])
@@ -1211,13 +1267,7 @@ def process_input(hklin, labin, n_bins, free, xyzins, source, d_max=None, d_min=
1211
1267
 
1212
1268
  if hkldata.df.empty:
1213
1269
  raise RuntimeError("No data in hkl data")
1214
-
1215
- if space_group is None:
1216
- sg_use = None
1217
- else:
1218
- sg_use = gemmi.SpaceGroup(space_group)
1219
- logger.writeln(f"Space group overridden by user. Using {sg_use.xhm()}")
1220
-
1270
+
1221
1271
  if sts:
1222
1272
  assert source in ["electron", "xray", "neutron"]
1223
1273
  for st in sts:
@@ -1227,23 +1277,8 @@ def process_input(hklin, labin, n_bins, free, xyzins, source, d_max=None, d_min=
1227
1277
  logger.writeln("Warning: unit cell mismatch between model and reflection data")
1228
1278
  logger.writeln(" using unit cell from mtz")
1229
1279
 
1230
- for st in sts: st.cell = hkldata.cell # mtz cell is used in any case
1231
-
1232
- sg_st = sts[0].find_spacegroup() # may be None
1233
- if sg_use is None:
1234
- sg_use = hkldata.sg
1235
- if hkldata.sg != sg_st:
1236
- if st.cell.is_crystal() and sg_st and sg_st.laue_str() != hkldata.sg.laue_str():
1237
- raise RuntimeError("Crystal symmetry mismatch between model and data")
1238
- logger.writeln("Warning: space group mismatch between model and mtz")
1239
- if sg_st and sg_st.laue_str() == hkldata.sg.laue_str():
1240
- logger.writeln(" using space group from model")
1241
- sg_use = sg_st
1242
- else:
1243
- logger.writeln(" using space group from mtz")
1244
- logger.writeln("")
1245
-
1246
1280
  for st in sts:
1281
+ st.cell = hkldata.cell # mtz cell is used in any case
1247
1282
  st.spacegroup_hm = sg_use.xhm()
1248
1283
  st.setup_cell_images()
1249
1284
 
@@ -1251,19 +1286,36 @@ def process_input(hklin, labin, n_bins, free, xyzins, source, d_max=None, d_min=
1251
1286
  utils.model.remove_charge(sts)
1252
1287
  utils.model.check_atomsf(sts, source)
1253
1288
 
1254
- if sg_use is not None:
1255
- hkldata.sg = sg_use
1256
1289
  hkldata.switch_to_asu()
1257
1290
  hkldata.remove_systematic_absences()
1258
1291
  #hkldata.df = hkldata.df.astype({name: 'float64' for name in ["I","SIGI","FP","SIGFP"] if name in hkldata.df})
1259
- d_min_data = hkldata.d_min_max(newlabels)[0]
1260
- if d_min is None and hkldata.d_min_max()[0] != d_min_data:
1261
- d_min = d_min_data
1292
+ d_min_max_data = hkldata.d_min_max(newlabels)
1293
+ if d_min is None and hkldata.d_min_max()[0] != d_min_max_data[0]:
1294
+ d_min = d_min_max_data[0]
1262
1295
  logger.writeln(f"Changing resolution to {d_min:.3f} A")
1263
1296
  if (d_min, d_max).count(None) != 2:
1264
1297
  hkldata = hkldata.copy(d_min=d_min, d_max=d_max)
1265
1298
  if hkldata.df.empty:
1266
1299
  raise RuntimeError("No data left in hkl data")
1300
+
1301
+ if hklin_free is not None:
1302
+ mtz2 = utils.fileio.read_mmhkl(hklin_free)
1303
+ if labin_free and labin_free not in mtz2.column_labels():
1304
+ raise RuntimeError(f"specified label ({labin_free}) not found in {hklin_free}")
1305
+ if not labin_free:
1306
+ tmp = utils.hkl.mtz_find_free_columns(mtz2)
1307
+ if tmp:
1308
+ labin_free = tmp[0]
1309
+ else:
1310
+ raise RuntimeError(f"Test flag label not found in {hklin_free}")
1311
+ tmp = utils.hkl.hkldata_from_mtz(mtz2, [labin_free], newlabels=["FREE"])
1312
+ tmp.sg = sg_use
1313
+ tmp.switch_to_asu()
1314
+ tmp.remove_systematic_absences()
1315
+ tmp = tmp.copy(d_min=d_min_max_data[0], d_max=d_min_max_data[1])
1316
+ hkldata.complete()
1317
+ tmp.complete()
1318
+ hkldata.merge(tmp.df[["H","K","L","FREE"]])
1267
1319
 
1268
1320
  hkldata.complete()
1269
1321
  hkldata.sort_by_resolution()
@@ -1271,7 +1323,7 @@ def process_input(hklin, labin, n_bins, free, xyzins, source, d_max=None, d_min=
1271
1323
  hkldata.calc_centric()
1272
1324
 
1273
1325
  if "FREE" in hkldata.df and free is None:
1274
- free = hkldata.guess_free_number(newlabels[0])
1326
+ free = hkldata.guess_free_number(newlabels[0]) # also check NaN
1275
1327
 
1276
1328
  if n_bins is None:
1277
1329
  if n_per_bin is None:
@@ -1297,8 +1349,6 @@ def process_input(hklin, labin, n_bins, free, xyzins, source, d_max=None, d_min=
1297
1349
  logger.writeln("n_per_bin={} requested for {}. n_bins set to {}".format(n_per_bin, use, n_bins))
1298
1350
 
1299
1351
  hkldata.setup_binning(n_bins=n_bins)
1300
- logger.writeln("Data completeness: {:.2f}%".format(hkldata.completeness()*100.))
1301
-
1302
1352
  fc_labs = ["FC{}".format(i) for i, _ in enumerate(sts)]
1303
1353
 
1304
1354
  # Create a centric selection table for faster look up
@@ -1348,6 +1398,14 @@ def process_input(hklin, labin, n_bins, free, xyzins, source, d_max=None, d_min=
1348
1398
  stats.loc[i_bin, "n_test"] = n_test
1349
1399
 
1350
1400
  stats["completeness"] = stats["n_obs"] / stats["n_all"] * 100
1401
+ logger.writeln("Data completeness: {:.2%}".format(stats["n_obs"].sum() / stats["n_all"].sum()))
1402
+ if ints_bak is not None:
1403
+ binner = gemmi.Binner()
1404
+ binner.setup(n_bins, gemmi.Binner.Method.Dstar2, ints_bak)
1405
+ bin_stats = ints_bak.calculate_merging_stats(binner, use_weights="X")
1406
+ stats["CC1/2"] = [stats.cc_half() for stats in bin_stats]
1407
+ hkldata.binned_df["CC*"] = numpy.sqrt(2 * stats["CC1/2"] / (1 + stats["CC1/2"]))
1408
+
1351
1409
  logger.writeln(stats.to_string())
1352
1410
  return hkldata, sts, fc_labs, centric_and_selections, free
1353
1411
  # process_input()
@@ -1551,7 +1609,9 @@ def main(args):
1551
1609
  use=args.use,
1552
1610
  max_bins=30,
1553
1611
  keep_charges=args.keep_charges,
1554
- space_group=args.spacegroup)
1612
+ space_group=args.spacegroup,
1613
+ hklin_free=args.hklin_free,
1614
+ labin_free=args.labin_free)
1555
1615
  except RuntimeError as e:
1556
1616
  raise SystemExit("Error: {}".format(e))
1557
1617
 
@@ -1623,7 +1683,7 @@ def main(args):
1623
1683
  if twin_data:
1624
1684
  labs = ["F_est", "F_exp"]
1625
1685
  elif is_int:
1626
- labs = ["I", "SIGI"]
1686
+ labs = ["I", "SIGI", "F_est"]
1627
1687
  else:
1628
1688
  labs = ["FP", "SIGFP"]
1629
1689
  labs.extend(["FOM", "FWT", "DELFWT", "FC", "DFC"])
servalcat/xtal/twin.py CHANGED
@@ -37,11 +37,10 @@ def find_twin_domains_from_data(hkldata, max_oblique=5, min_alpha=0.05):
37
37
  nums.append([])
38
38
  rs = []
39
39
  for i_op, op in enumerate(ops):
40
+ cc = r = numpy.nan
40
41
  ii = numpy.array(twin_data.pairs(i_op, i_bin))
41
- val = numpy.all(numpy.isfinite(Io[ii]), axis=1)
42
- if numpy.sum(val) == 0:
43
- cc = r = numpy.nan
44
- else:
42
+ val = numpy.all(numpy.isfinite(Io[ii]), axis=1) if ii.size != 0 else []
43
+ if numpy.sum(val) != 0:
45
44
  cc = numpy.corrcoef(Io[ii][val].T)[0,1]
46
45
  r = numpy.sum(numpy.abs(Io[ii][val, 0] - Io[ii][val, 1])) / numpy.sum(Io[ii][val])
47
46
  ratio = (1 - numpy.sqrt(1 - cc**2)) / cc
@@ -1,16 +1,16 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.2
2
2
  Name: servalcat
3
- Version: 0.4.100
3
+ Version: 0.4.105
4
4
  Summary: Structure refinement and validation for crystallography and single particle analysis
5
5
  Author: Keitaro Yamashita, Garib N. Murshudov
6
6
  License: MPL-2.0
7
- Project-URL: Repository, https://github.com/keitaroyam/servalcat
7
+ Project-URL: repository, https://github.com/keitaroyam/servalcat
8
8
  Requires-Python: >=3.8
9
9
  Requires-Dist: packaging
10
10
  Requires-Dist: numpy>=1.15
11
11
  Requires-Dist: scipy
12
12
  Requires-Dist: pandas>=1.1.0
13
- Requires-Dist: gemmi==0.7.0
13
+ Requires-Dist: gemmi==0.7.1
14
14
  Description-Content-Type: text/markdown
15
15
 
16
16
  # Servalcat
@@ -40,7 +40,7 @@ pip install servalcat
40
40
  ```
41
41
  will install the stable version.
42
42
 
43
- The required GEMMI version is now [v0.7.0](https://github.com/project-gemmi/gemmi/releases/tag/v0.7.0). It may not work with the latest gemmi code from the github. The policy is in the main branch I only push the code that works with the latest package of GEMMI.
43
+ The required GEMMI version is now [v0.7.1](https://github.com/project-gemmi/gemmi/releases/tag/v0.7.1). It may not work with the latest gemmi code from the github. The policy is in the main branch I only push the code that works with the latest package of GEMMI.
44
44
 
45
45
  To use the Refmac5 related commands, you also need to install [CCP4](https://www.ccp4.ac.uk/). For "No Refmac5" commands, you may just need [the monomer library](https://github.com/MonomerLibrary/monomers) if CCP4 is not installed.
46
46
 
@@ -1,45 +1,45 @@
1
- servalcat/__init__.py,sha256=bOvfsOFrPkgPBtZIILjkf4hYnPjPRDQYcfbDpTzod3k,242
1
+ servalcat/__init__.py,sha256=h7BOGz-QcZhMfvflyNwkvrvZA4n4eOWgYklEDpKwUZo,242
2
2
  servalcat/__main__.py,sha256=XUM193aDwZAEQY02VzvZasAzD6AYEM-_A4wqy93KDWE,4190
3
- servalcat/ext.cp311-win_amd64.pyd,sha256=40LmEqSy1DI73pjCEWtd53GtpV4TNaQdDif5E7qlwq8,1335808
3
+ servalcat/ext.cp311-win_amd64.pyd,sha256=Ff9-pb99ApU1n3klxqwvoAJXxwRGRJx2MAZJ6gh04_w,1326592
4
4
  servalcat/refine/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
5
  servalcat/refine/cgsolve.py,sha256=Xz7EwGI-mr4nnbfARyfFjAqbJbPWGH9qNWQ0GpltDuc,3172
6
- servalcat/refine/refine.py,sha256=P6epHQZLpz3Tl2fuGrdCp6WOxuzvik7YJJTBsd4zuHg,44941
6
+ servalcat/refine/refine.py,sha256=Q4qYmG5g6eWNlAaysS4Bto46jHiNzYpJg3wAwEqVBjs,45284
7
7
  servalcat/refine/refine_geom.py,sha256=RMfhmBb-jlSFZoMhV3QONev9U2ZuHuPyjSslhECMYXo,11585
8
- servalcat/refine/refine_spa.py,sha256=tc1ZW-jJoIga5tfYA3DTLFg6QsgiN9xc_cDQKJQgtcc,19687
9
- servalcat/refine/refine_xtal.py,sha256=5qrLrUawTRuXVGoaBNpZeTJiHB6jYrJeNnARNFe9kvQ,14841
8
+ servalcat/refine/refine_spa.py,sha256=LpXvIlOBsJ-svUk4YPmbkwb3fjLtO__8BBmxJdzUOsE,19542
9
+ servalcat/refine/refine_xtal.py,sha256=UELKLpzc8CoLd81qtHRVa87oKYDzdhpdNHqMrNNr0kU,15403
10
10
  servalcat/refine/spa.py,sha256=7aYSnSty0fSe46P_RQ79Bd_8RwD88k3uAaLHVsz6yHc,6616
11
11
  servalcat/refine/xtal.py,sha256=2GUcJZhY35CYr3P6StpjEnPdgBtLWG10mAuN-X4cXy4,14911
12
12
  servalcat/refmac/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
13
13
  servalcat/refmac/exte.py,sha256=LRPej2yCZ4RoWJnEQldILEiUED6S_w1lgutWAdhu9_o,9201
14
14
  servalcat/refmac/refmac_keywords.py,sha256=WPLDQdk91i5yKZ0QcfaTQ6qL-_qBv7_91eUv4Is9j54,27288
15
- servalcat/refmac/refmac_wrapper.py,sha256=7FuYyWqN1UvSilFECWOSmWmYObXFLvO49PWkRbwBPHQ,18233
15
+ servalcat/refmac/refmac_wrapper.py,sha256=nNFCMaSgxce4NlZ8GaqsqafDmgGkUSQIl8_EiC6djFU,20240
16
16
  servalcat/spa/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
17
- servalcat/spa/fofc.py,sha256=DONPNvHPP9odKGNekpo781D_skjReUEytQ1wHBJxGwc,23592
17
+ servalcat/spa/fofc.py,sha256=u_ly-aFGWSttsRdlnqJhwuJ9_gwoY9LHXVvy3QTS0vE,23607
18
18
  servalcat/spa/fsc.py,sha256=Rszx-zzJiX5iSY-Qp6q5uCrEhE3a5eyW-m_szT5itLA,17839
19
19
  servalcat/spa/localcc.py,sha256=FJMXiC0Po5eb1Le404mgNDj9VcxH5CRR9F5B7uDZn5w,8173
20
20
  servalcat/spa/realspcc_from_var.py,sha256=x3NaqGKpGmQyh3qp-SJ9kTA_hP2EvxBOIszYbV2EPKk,5403
21
- servalcat/spa/run_refmac.py,sha256=bHSj-8_RLdrfHTCyNWoMtXsrNT37AfpONZ8D3jR01ZE,50527
21
+ servalcat/spa/run_refmac.py,sha256=Mxek2DeJebtO8UdJyzfdw05pqFJ4SOEiw_tmhBH6nlU,50526
22
22
  servalcat/spa/shift_maps.py,sha256=G_EddBcdyncl2bhW7I-4YF0ZFtX9Q95a2SIJ9rpPeR4,13561
23
23
  servalcat/spa/shiftback.py,sha256=EtITj8Nllz-MWE6oJrxruDWeT0q7tS3RXCKtpWMuHNg,4801
24
24
  servalcat/spa/translate.py,sha256=PGf1vHh_b_mxDta_CAghY7QBRVPzHSaGLDhrnafqCdA,5228
25
25
  servalcat/utils/__init__.py,sha256=4umrD2fhG1QAQppDBQM95-6y8QYbtxr7Mi_-Nkc3XOA,1172
26
- servalcat/utils/commands.py,sha256=fEWF6HesIAuxnzbaKAPws_QaIPxJUQc6DVthHeex_Vk,72503
27
- servalcat/utils/fileio.py,sha256=3fyHTQ62sibP3cUYtX8D9G7cC6qelkfPKnbA47MqPOo,30246
26
+ servalcat/utils/commands.py,sha256=uHQs8Z7aKn4HxvyC3URA8-XwowVfy_CGPikx2RbJ4Fs,72575
27
+ servalcat/utils/fileio.py,sha256=uuhOejD8e86XTrOUHdk-zMHqUvRvQ9BYwKa6W9nvUyQ,31951
28
28
  servalcat/utils/generate_operators.py,sha256=fCrcTcciRn-6fpFtHYvf1oPXAObjuqR3WQejz5d0RrA,10980
29
29
  servalcat/utils/hkl.py,sha256=pciLeh7dPxuF0-gmKRAI0h6zhYCA7QWH91LEBLzAHOA,30536
30
30
  servalcat/utils/logger.py,sha256=25uXUw8xCjvd9bnzIQ0Nq1C4MNIqEXSG-xScEnXGmLM,4727
31
31
  servalcat/utils/maps.py,sha256=1Gm54nEvPj4peCuo0Rx-8_gvRD1pr2qECq7U9N6mrVw,13570
32
- servalcat/utils/model.py,sha256=foNzqFxN306Ir40dpU4Djxx0sZ7Gbh0ZIJj-e3ifTPg,31345
32
+ servalcat/utils/model.py,sha256=CT1pjoKrEk8SbJt8wT6WulIYYBpSwY97uV1Ra5rxrc4,31391
33
33
  servalcat/utils/refmac.py,sha256=dY92UZo2orgZJOEmPacCek8PMj-ydCp6-pR0rdsAVG8,31682
34
34
  servalcat/utils/restraints.py,sha256=hclAJ3CKzIqeyC-I0rH8TSwikwQN1k8dUtYWKC0OAnU,37741
35
- servalcat/utils/symmetry.py,sha256=PSSD-S_j_t1m4E9F8Fd9RJqTWyKf92zLCjSED7iXFkU,12164
35
+ servalcat/utils/symmetry.py,sha256=XEocxhKgeeESNd7KPAiaj09-5BwgLcpg-L_-GnHZziM,12325
36
36
  servalcat/xtal/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
37
37
  servalcat/xtal/french_wilson.py,sha256=u4wzHuotO8-avPkhuEylg9WmMZYdGHYz1IvG07uqW3M,12045
38
38
  servalcat/xtal/run_refmac_small.py,sha256=_Rw7wz7_yAlQgDqjJ_njeK6ZqN_r7KAakoor9WeNhxI,10634
39
- servalcat/xtal/sigmaa.py,sha256=c2WuFu5cOew-TTJyYkJDaIykbRqFuKddGqTQblWfH6U,76904
40
- servalcat/xtal/twin.py,sha256=Z1WY3K61SFE4EcCo3C2lUxByhx68nkZMlruUNr5jimc,6325
41
- servalcat-0.4.100.dist-info/METADATA,sha256=fY8VFfVql5PFlkJdbmAFaPOH9WVm8MJ7oKedUyyL9ys,2742
42
- servalcat-0.4.100.dist-info/WHEEL,sha256=kXCl1J14PkmxQKXf5U_5vxmme_OmC3Ydcral7u0yA3M,106
43
- servalcat-0.4.100.dist-info/entry_points.txt,sha256=G1mDxhOCdF3umYz4k0kfwJbSdYSKqhvQdGCmrP8FRAY,111
44
- servalcat-0.4.100.dist-info/licenses/LICENSE,sha256=JoTeFzAOCkNGhvHsf4r2BFIHpLRXo_4EsrnOZV58XVA,17098
45
- servalcat-0.4.100.dist-info/RECORD,,
39
+ servalcat/xtal/sigmaa.py,sha256=mY3wo6fRGG3TgTbuer2oLFuGzgQjH2yCkaf4Dy02eIw,79996
40
+ servalcat/xtal/twin.py,sha256=2Hmx8GYxDwcBvtDIYfqCZkoFEDKNdtKBQfkNhW2ZeXQ,6326
41
+ servalcat-0.4.105.dist-info/METADATA,sha256=LIq9HmLOpQpWxOglnAHph9OU8I3rOvugdYVn6Oupueo,2742
42
+ servalcat-0.4.105.dist-info/WHEEL,sha256=xGZmaOXqwLlhmjR8ikRHIC1Wm-k3ULVrL-oi9GC-Mmc,106
43
+ servalcat-0.4.105.dist-info/entry_points.txt,sha256=G1mDxhOCdF3umYz4k0kfwJbSdYSKqhvQdGCmrP8FRAY,111
44
+ servalcat-0.4.105.dist-info/licenses/LICENSE,sha256=JoTeFzAOCkNGhvHsf4r2BFIHpLRXo_4EsrnOZV58XVA,17098
45
+ servalcat-0.4.105.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: scikit-build-core 0.10.7
2
+ Generator: scikit-build-core 0.11.1
3
3
  Root-Is-Purelib: false
4
4
  Tag: cp311-cp311-win_amd64
5
5