servalcat 0.4.131__cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.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 (45) hide show
  1. servalcat/__init__.py +10 -0
  2. servalcat/__main__.py +120 -0
  3. servalcat/ext.cpython-314t-x86_64-linux-gnu.so +0 -0
  4. servalcat/refine/__init__.py +0 -0
  5. servalcat/refine/cgsolve.py +100 -0
  6. servalcat/refine/refine.py +1162 -0
  7. servalcat/refine/refine_geom.py +245 -0
  8. servalcat/refine/refine_spa.py +400 -0
  9. servalcat/refine/refine_xtal.py +339 -0
  10. servalcat/refine/spa.py +151 -0
  11. servalcat/refine/xtal.py +312 -0
  12. servalcat/refmac/__init__.py +0 -0
  13. servalcat/refmac/exte.py +191 -0
  14. servalcat/refmac/refmac_keywords.py +660 -0
  15. servalcat/refmac/refmac_wrapper.py +423 -0
  16. servalcat/spa/__init__.py +0 -0
  17. servalcat/spa/fofc.py +488 -0
  18. servalcat/spa/fsc.py +391 -0
  19. servalcat/spa/localcc.py +197 -0
  20. servalcat/spa/realspcc_from_var.py +128 -0
  21. servalcat/spa/run_refmac.py +979 -0
  22. servalcat/spa/shift_maps.py +293 -0
  23. servalcat/spa/shiftback.py +137 -0
  24. servalcat/spa/translate.py +129 -0
  25. servalcat/utils/__init__.py +35 -0
  26. servalcat/utils/commands.py +1629 -0
  27. servalcat/utils/fileio.py +836 -0
  28. servalcat/utils/generate_operators.py +296 -0
  29. servalcat/utils/hkl.py +811 -0
  30. servalcat/utils/logger.py +140 -0
  31. servalcat/utils/maps.py +345 -0
  32. servalcat/utils/model.py +933 -0
  33. servalcat/utils/refmac.py +759 -0
  34. servalcat/utils/restraints.py +888 -0
  35. servalcat/utils/symmetry.py +298 -0
  36. servalcat/xtal/__init__.py +0 -0
  37. servalcat/xtal/french_wilson.py +262 -0
  38. servalcat/xtal/run_refmac_small.py +240 -0
  39. servalcat/xtal/sigmaa.py +1954 -0
  40. servalcat/xtal/twin.py +316 -0
  41. servalcat-0.4.131.dist-info/METADATA +60 -0
  42. servalcat-0.4.131.dist-info/RECORD +45 -0
  43. servalcat-0.4.131.dist-info/WHEEL +6 -0
  44. servalcat-0.4.131.dist-info/entry_points.txt +4 -0
  45. servalcat-0.4.131.dist-info/licenses/LICENSE +373 -0
@@ -0,0 +1,979 @@
1
+ """
2
+ Author: "Keitaro Yamashita, Garib N. Murshudov"
3
+ MRC Laboratory of Molecular Biology
4
+
5
+ This software is released under the
6
+ Mozilla Public License, version 2.0; see LICENSE.
7
+ """
8
+ from __future__ import absolute_import, division, print_function, generators
9
+ import gemmi
10
+ import numpy
11
+ import json
12
+ import os
13
+ import shutil
14
+ import argparse
15
+ from servalcat.utils import logger
16
+ from servalcat import utils
17
+ from servalcat import spa # this is not a right style
18
+ from servalcat.spa import shift_maps
19
+ from servalcat.refmac.refmac_wrapper import prepare_crd
20
+
21
+ def add_arguments(parser):
22
+ parser.description = 'Run REFMAC5 for SPA'
23
+
24
+ parser.add_argument('--exe', default="refmac5",
25
+ help='refmac5 binary (default: %(default)s)')
26
+ parser.add_argument("--monlib",
27
+ help="Monomer library path. Default: $CLIBD_MON")
28
+ # sfcalc options
29
+ group = parser.add_mutually_exclusive_group(required=True)
30
+ group.add_argument("--halfmaps", nargs=2, help="Input half map files")
31
+ group.add_argument("--map", help="Use this only if you really do not have half maps.")
32
+ parser.add_argument('--mask',
33
+ help='Mask file')
34
+ parser.add_argument('--model', required=True,
35
+ help='Input atomic model file')
36
+ parser.add_argument('--mask_radius',
37
+ type=float, default=3,
38
+ help='')
39
+ parser.add_argument('--mask_soft_edge',
40
+ type=float, default=0,
41
+ help='Add soft edge to model mask. Should use with --no_sharpen_before_mask?')
42
+ parser.add_argument('--padding',
43
+ type=float,
44
+ help='Default: 2*mask_radius')
45
+ parser.add_argument('--no_mask', action='store_true')
46
+ parser.add_argument('--invert_mask', action='store_true', help='not for refinement.')
47
+ parser.add_argument('--pixel_size', type=float,
48
+ help='Override pixel size (A)')
49
+ parser.add_argument('--resolution',
50
+ type=float,
51
+ help='')
52
+ parser.add_argument('--no_trim',
53
+ action='store_true',
54
+ help='Keep original box (not recommended)')
55
+ parser.add_argument('--blur',
56
+ type=float, default=0,
57
+ help='Sharpening or blurring B')
58
+ utils.symmetry.add_symmetry_args(parser) # add --pg etc
59
+ parser.add_argument('--contacting_only', action="store_true", help="Filter out non-contacting NCS")
60
+ parser.add_argument('--ignore_symmetry', action='store_true',
61
+ help='Ignore symmetry information (MTRIX/_struct_ncs_oper) in the model file')
62
+ parser.add_argument('--find_links', action='store_true',
63
+ help='Automatically add links')
64
+ parser.add_argument("--b_before_mask", type=float,
65
+ help="sharpening B value for sharpen-mask-unsharpen procedure. By default it is determined automatically.")
66
+ parser.add_argument('--no_sharpen_before_mask', action='store_true',
67
+ help='By default half maps are sharpened before masking by std of signal and unsharpened after masking. This option disables it.')
68
+ parser.add_argument('--no_fix_microheterogeneity', action='store_true',
69
+ help='By default it will fix microheterogeneity for Refmac')
70
+ parser.add_argument('--no_fix_resi9999', action='store_true',
71
+ help='By default it will split chain if max residue number > 9999 which is not supported by Refmac')
72
+ parser.add_argument('--no_check_ncs_overlaps', action='store_true',
73
+ help='Disable model overlap (e.g. expanded model is used with --pg) test')
74
+ parser.add_argument('--no_check_ncs_map', action='store_true',
75
+ help='Disable map NCS consistency test')
76
+ parser.add_argument('--no_check_mask_with_model', action='store_true',
77
+ help='Disable mask test using model')
78
+ parser.add_argument("--prepare_only", action='store_true',
79
+ help="Stop before refinement")
80
+ parser.add_argument("--no_refmacat", action='store_true',
81
+ help="By default uses gemmi for crd/rst file preparation (do not use makecif)")
82
+ # run_refmac options
83
+ # TODO use group! like refmac options
84
+ parser.add_argument('--ligand', nargs="*", action="append",
85
+ help="restraint dictionary cif file(s)")
86
+ parser.add_argument('--bfactor', type=float,
87
+ help="reset all atomic B values to specified value")
88
+ parser.add_argument('--ncsr', default="local", choices=["local", "global"],
89
+ help="local or global NCS restrained (default: %(default)s)")
90
+ parser.add_argument('--ncycle', type=int, default=10,
91
+ help="number of cycles in Refmac (default: %(default)d)")
92
+ parser.add_argument('--tlscycle', type=int, default=0,
93
+ help="number of TLS cycles in Refmac (default: %(default)d)")
94
+ parser.add_argument('--tlsin',
95
+ help="TLS parameter input for Refmac")
96
+ parser.add_argument('--hydrogen', default="all", choices=["all", "yes", "no"],
97
+ help="all: add riding hydrogen atoms, yes: use hydrogen atoms if present, no: remove hydrogen atoms in input. "
98
+ "Default: %(default)s")
99
+ parser.add_argument('--jellybody', action='store_true',
100
+ help="Use jelly body restraints")
101
+ parser.add_argument('--jellybody_params', nargs=2, type=float,
102
+ metavar=("sigma", "dmax"), default=[0.01, 4.2],
103
+ help="Jelly body sigma and dmax (default: %(default)s)")
104
+ parser.add_argument('--hout', action='store_true', help="write hydrogen atoms in the output model")
105
+ group = parser.add_mutually_exclusive_group()
106
+ group.add_argument('--weight_auto_scale', type=float,
107
+ help="'weight auto' scale value. automatically determined from resolution and mask/box volume ratio if unspecified")
108
+ parser.add_argument('--keywords', nargs='+', action="append",
109
+ help="refmac keyword(s)")
110
+ parser.add_argument('--keyword_file', nargs='+', action="append",
111
+ help="refmac keyword file(s)")
112
+ parser.add_argument('--external_restraints_json')
113
+ parser.add_argument('--show_refmac_log', action='store_true',
114
+ help="show all Refmac log instead of summary")
115
+ parser.add_argument('--output_prefix', default="refined",
116
+ help='output file name prefix (default: %(default)s)')
117
+ parser.add_argument('--cross_validation', action='store_true',
118
+ help='Run cross validation')
119
+ parser.add_argument('--cross_validation_method', default="shake", choices=["throughout", "shake"],
120
+ help="shake: randomize a model refined against a full map and then refine it against a half map, "
121
+ "throughout: use only a half map for refinement (another half map is used for error estimation) "
122
+ "Default: %(default)s")
123
+ parser.add_argument('--shake_radius', default=0.3,
124
+ help='Shake rmsd in case of --cross_validation_method=shake (default: %(default).1f)')
125
+ group = parser.add_mutually_exclusive_group()
126
+ group.add_argument('--mask_for_fofc', help="Mask file for Fo-Fc map calculation")
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="maps mtz will have smaller cell (if --mask_for_fofc is given)")
129
+ parser.add_argument("--fsc_resolution", type=float,
130
+ help="High resolution limit for FSC calculation. Default: Nyquist")
131
+
132
+ # add_arguments()
133
+
134
+ def parse_args(arg_list):
135
+ parser = argparse.ArgumentParser()
136
+ add_arguments(parser)
137
+ return parser.parse_args(arg_list)
138
+ # parse_args()
139
+
140
+ def calc_fsc(st, output_prefix, maps, d_min, mask, mask_radius, soft_edge, b_before_mask, no_sharpen_before_mask, make_hydrogen, monlib,
141
+ blur=0, d_min_fsc=None, cross_validation=False, cross_validation_method=None, st_sr=None,
142
+ source="electron"):
143
+ # st_sr: shaken-and-refined st in case of cross_validation_method=="shake"
144
+ if cross_validation:
145
+ assert len(maps) == 2
146
+ assert cross_validation_method in ("shake", "throughout")
147
+ if cross_validation and cross_validation_method == "shake":
148
+ assert st_sr is not None
149
+ else:
150
+ assert st_sr is None
151
+
152
+ logger.writeln("Calculating map-model FSC..")
153
+ ret = {"summary": {}}
154
+
155
+ if d_min_fsc is None:
156
+ d_min_fsc = utils.maps.nyquist_resolution(maps[0][0])
157
+ logger.writeln(" --fsc_resolution is not specified. Using Nyquist resolution: {:.2f}".format(d_min_fsc))
158
+
159
+ st = st.clone()
160
+ if st_sr is not None: st_sr = st_sr.clone()
161
+
162
+ if make_hydrogen == "all":
163
+ utils.restraints.add_hydrogens(st, monlib)
164
+ if st_sr is not None: utils.restraints.add_hydrogens(st_sr, monlib)
165
+
166
+ if mask is not None or mask_radius is not None:
167
+ if mask is None:
168
+ assert maps[0][0].unit_cell == st.cell
169
+ mask = utils.maps.mask_from_model(st, mask_radius, soft_edge=soft_edge, grid=maps[0][0])
170
+ if no_sharpen_before_mask or len(maps) < 2:
171
+ for ma in maps: ma[0].array[:] *= mask
172
+ else:
173
+ # It seems we need different B for different resolution limit
174
+ if b_before_mask is None: b_before_mask = determine_b_before_mask(st, maps, maps[0][1], mask, d_min_fsc)
175
+ maps = utils.maps.sharpen_mask_unsharpen(maps, mask, d_min_fsc, b=b_before_mask)
176
+
177
+ hkldata = utils.maps.mask_and_fft_maps(maps, d_min_fsc)
178
+ hkldata.df["FC"] = utils.model.calc_fc_fft(st, d_min_fsc - 1e-6, monlib=monlib, source=source,
179
+ miller_array=hkldata.miller_array())
180
+ # XXX didn't apply mask to FC!!
181
+ labs_fc = ["FC"]
182
+
183
+ if st_sr is not None:
184
+ hkldata.df["FC_sr"] = utils.model.calc_fc_fft(st_sr, d_min_fsc - 1e-6, monlib=monlib, source=source,
185
+ miller_array=hkldata.miller_array())
186
+ labs_fc.append("FC_sr")
187
+
188
+ if blur != 0:
189
+ logger.writeln(" Unblurring Fc with B={} for FSC calculation".format(blur))
190
+ unblur = numpy.exp(blur/hkldata.d_spacings().to_numpy()**2/4.)
191
+ for lab in labs_fc:
192
+ hkldata.df[lab] *= unblur
193
+
194
+ hkldata.setup_relion_binning("stat")
195
+ stats = spa.fsc.calc_fsc_all(hkldata, labs_fc=labs_fc, lab_f="FP",
196
+ labs_half=["F_map1", "F_map2"] if len(maps)==2 else None)
197
+
198
+ hkldata2 = hkldata.copy(d_min=d_min) # for FSCaverage at resolution for refinement # XXX more efficient way
199
+ hkldata2.setup_relion_binning("stat")
200
+ stats2 = spa.fsc.calc_fsc_all(hkldata2, labs_fc=labs_fc, lab_f="FP",
201
+ labs_half=["F_map1", "F_map2"] if len(maps)==2 else None)
202
+
203
+ if "fsc_half" in stats:
204
+ with numpy.errstate(invalid="ignore"): # XXX negative fsc results in nan!
205
+ stats.loc[:,"fsc_full_sqrt"] = numpy.sqrt(2*stats.fsc_half/(1+stats.fsc_half))
206
+
207
+ logger.writeln(stats.to_string()+"\n")
208
+
209
+ # remove and rename columns
210
+ for s in (stats, stats2):
211
+ s.rename(columns=dict(fsc_FC_full="fsc_model", Rcmplx_FC_full="Rcmplx"), inplace=True)
212
+ if cross_validation:
213
+ if cross_validation_method == "shake":
214
+ s.drop(columns=["fsc_FC_half1", "fsc_FC_half2", "fsc_FC_sr_full", "Rcmplx_FC_sr_full"], inplace=True)
215
+ s.rename(columns=dict(fsc_FC_sr_half1="fsc_model_half1",
216
+ fsc_FC_sr_half2="fsc_model_half2"), inplace=True)
217
+ else:
218
+ s.rename(columns=dict(fsc_FC_half1="fsc_model_half1",
219
+ fsc_FC_half2="fsc_model_half2"), inplace=True)
220
+ else:
221
+ s.drop(columns=[x for x in s if x.startswith("fsc_FC") and x.endswith(("half1","half2"))], inplace=True)
222
+
223
+ # FSCaverages
224
+ ret["summary"]["d_min"] = d_min
225
+ ret["summary"]["FSCaverage"] = spa.fsc.fsc_average(stats2.ncoeffs, stats2.fsc_model)
226
+ if cross_validation:
227
+ ret["summary"]["FSCaverage_half1"] = spa.fsc.fsc_average(stats2.ncoeffs, stats2.fsc_model_half1)
228
+ ret["summary"]["FSCaverage_half2"] = spa.fsc.fsc_average(stats2.ncoeffs, stats2.fsc_model_half2)
229
+ fscavg_text = "Map-model FSCaverages (at {:.2f} A):\n".format(d_min)
230
+ fscavg_text += " FSCaverage(full) = {: .4f}\n".format(ret["summary"]["FSCaverage"])
231
+ if cross_validation:
232
+ fscavg_text += "Cross-validated map-model FSCaverages:\n"
233
+ fscavg_text += " FSCaverage(half1)= {: .4f}\n".format(ret["summary"]["FSCaverage_half1"])
234
+ fscavg_text += " FSCaverage(half2)= {: .4f}\n".format(ret["summary"]["FSCaverage_half2"])
235
+
236
+ # for loggraph
237
+ fsc_logfile = "{}_fsc.log".format(output_prefix)
238
+ with open(fsc_logfile, "w") as ofs:
239
+ columns = "1/resol^2 ncoef ln(Mn(|Fo|^2)) ln(Mn(|Fc|^2)) FSC(full,model) FSC_half FSC_full_sqrt FSC(half1,model) FSC(half2,model) Rcmplx(full,model)".split()
240
+
241
+ ofs.write("$TABLE: Map-model FSC after refinement:\n")
242
+ if len(maps) == 2:
243
+ if cross_validation: fsc_cols = [5,6,7,8,9]
244
+ else: fsc_cols = [5,6,7]
245
+ else: fsc_cols = [5]
246
+ fsc_cols.append(10)
247
+ if len(maps) == 2: ofs.write("$GRAPHS: FSC :A:1,5,6,7:\n")
248
+ else: ofs.write("$GRAPHS: FSC :A:1,5:\n")
249
+ if cross_validation: ofs.write(": cross-validated FSC :A:1,8,9:\n".format(",".join(map(str,fsc_cols))))
250
+ ofs.write(": Rcmplx :A:1,{}:\n".format(4+len(fsc_cols)))
251
+ ofs.write(": ln(Mn(|F|^2)) :A:1,3,4:\n")
252
+ ofs.write(": number of Fourier coeffs :A:1,2:\n")
253
+ ofs.write("$$ {}$$\n".format(" ".join(columns[:4]+[columns[i-1] for i in fsc_cols])))
254
+ ofs.write("$$\n")
255
+
256
+ plot_columns = ["d_min", "ncoeffs", "power_FP", "power_FC", "fsc_model"]
257
+ if len(maps) == 2:
258
+ plot_columns.extend(["fsc_half", "fsc_full_sqrt"])
259
+ if cross_validation:
260
+ plot_columns.extend(["fsc_model_half1", "fsc_model_half2"])
261
+ plot_columns.append("Rcmplx")
262
+ with numpy.errstate(divide="ignore"):
263
+ log_format = lambda x: "{:.3f}".format(numpy.log(x))
264
+ ofs.write(stats.to_string(header=False, index=False, index_names=False, columns=plot_columns,
265
+ formatters=dict(d_min=lambda x: "{:.4f}".format(1/x**2),
266
+ power_FP=log_format, power_FC=log_format)))
267
+ ofs.write("\n")
268
+ ofs.write("$$\n\n")
269
+ ofs.write(fscavg_text)
270
+
271
+ logger.write(fscavg_text)
272
+ logger.writeln("Run loggraph {} to see plots.".format(fsc_logfile))
273
+
274
+ # write json
275
+ with open("{}_fsc.json".format(output_prefix), "w") as f:
276
+ json.dump(stats.to_dict("records"), f, indent=True)
277
+ ret["binned"] = stats2.to_dict(orient="records")
278
+ return fscavg_text, ret
279
+ # calc_fsc()
280
+
281
+ def calc_fofc(st, st_expanded, maps, monlib, model_format, args, diffmap_prefix="diffmap", source="electron"):
282
+ logger.writeln("Starting Fo-Fc calculation..")
283
+ if not args.halfmaps: logger.writeln(" with limited functionality because half maps were not provided")
284
+ logger.writeln(" model: {}".format(args.output_prefix+model_format))
285
+
286
+ # for Fo-Fc in case of helical reconstruction, expand model more
287
+ # XXX should we do it for FSC calculation also? Probably we should not do sharpen-unsharpen procedure for FSC calc either.
288
+ if args.twist is not None:
289
+ logger.writeln("Generating all helical copies in the box")
290
+ st_expanded = st.clone()
291
+ utils.symmetry.update_ncs_from_args(args, st_expanded, map_and_start=maps[0], filter_contacting=False)
292
+ utils.model.expand_ncs(st_expanded)
293
+ utils.fileio.write_model(st_expanded, args.output_prefix+"_expanded_all", pdb=True, cif=True)
294
+
295
+ if args.mask_for_fofc:
296
+ logger.writeln(" mask: {}".format(args.mask_for_fofc))
297
+ mask = utils.fileio.read_ccp4_map(args.mask_for_fofc)[0]
298
+ elif args.mask_radius_for_fofc:
299
+ logger.writeln(" mask: using refined model with radius of {} A".format(args.mask_radius_for_fofc))
300
+ mask = utils.maps.mask_from_model(st_expanded, args.mask_radius_for_fofc, grid=maps[0][0]) # use soft edge?
301
+ else:
302
+ logger.writeln(" mask: not used")
303
+ mask = None
304
+
305
+ hkldata, map_labs, stats_str = spa.fofc.calc_fofc(st_expanded, args.resolution, maps, mask=mask, monlib=monlib,
306
+ half1_only=(args.cross_validation and args.cross_validation_method == "throughout"),
307
+ sharpening_b=None if args.halfmaps else 0., # assume already sharpened if fullmap is given
308
+ source=source)
309
+ spa.fofc.write_files(hkldata, map_labs, maps[0][1], stats_str,
310
+ mask=mask, output_prefix=diffmap_prefix,
311
+ trim_map=mask is not None, trim_mtz=args.trim_fofc_mtz)
312
+
313
+ # Create Coot script
314
+ spa.fofc.write_coot_script("{}_coot.py".format(args.output_prefix),
315
+ model_file="{}.pdb".format(args.output_prefix), # as Coot is not good at mmcif file..
316
+ mtz_file="{}_maps.mtz".format(diffmap_prefix),
317
+ contour_fo=None if mask is None else 1.2,
318
+ contour_fofc=None if mask is None else 3.0,
319
+ ncs_ops=st.ncs)
320
+
321
+ # Create ChimeraX script
322
+ spa.fofc.write_chimerax_script(cxc_out="{}_chimerax.cxc".format(args.output_prefix),
323
+ model_file="{}.mmcif".format(args.output_prefix), # ChimeraX handles mmcif just fine
324
+ fo_mrc_file="{}_normalized_fo.mrc".format(diffmap_prefix),
325
+ fofc_mrc_file="{}_normalized_fofc.mrc".format(diffmap_prefix))
326
+ # calc_fofc()
327
+
328
+ def write_final_summary(st, refmac_summary, fscavg_text, output_prefix, is_mask_given):
329
+ if len(refmac_summary["cycles"]) > 1 and "actual_weight" in refmac_summary["cycles"][-2]:
330
+ final_weight = refmac_summary["cycles"][-2]["actual_weight"]
331
+ else:
332
+ final_weight = "???"
333
+
334
+ adpstats_txt = ""
335
+ adp_stats = utils.model.adp_stats_per_chain(st[0])
336
+ max_chain_len = max([len(x[0]) for x in adp_stats])
337
+ max_num_len = max([len(str(x[1])) for x in adp_stats])
338
+ for chain, natoms, qs in adp_stats:
339
+ adpstats_txt += " Chain {0:{1}s}".format(chain, max_chain_len) if chain!="*" else " {0:{1}s}".format("All", max_chain_len+6)
340
+ adpstats_txt += " ({0:{1}d} atoms) min={2:5.1f} median={3:5.1f} max={4:5.1f} A^2\n".format(natoms, max_num_len, qs[0],qs[2],qs[4])
341
+
342
+ if is_mask_given:
343
+ map_peaks_str = """\
344
+ List Fo-Fc map peaks in the ASU:
345
+ servalcat util map_peaks --map diffmap_normalized_fofc.mrc --model {prefix}.pdb --abs_level 4.0 \
346
+ """.format(prefix=output_prefix)
347
+ else:
348
+ map_peaks_str = "WARNING: --mask_for_fofc was not given, so the Fo-Fc map was not normalized."
349
+
350
+ logger.writeln("""
351
+ =============================================================================
352
+ * Final Summary *
353
+
354
+ Rmsd from ideal
355
+ bond lengths: {rmsbond} A
356
+ bond angles: {rmsangle} deg
357
+
358
+ {fscavgs}
359
+ Run loggraph {fsclog} to see plots
360
+
361
+ ADP statistics
362
+ {adpstats}
363
+
364
+ Weight used: {final_weight}
365
+ If you want to change the weight, give larger (looser restraints)
366
+ or smaller (tighter) value to --weight_auto_scale=.
367
+
368
+ Open refined model and maps mtz with COOT:
369
+ coot --script {prefix}_coot.py
370
+
371
+ Open refined model, map and difference map with ChimeraX/ISOLDE:
372
+ chimerax {prefix}_chimerax.cxc
373
+
374
+ {map_peaks_msg}
375
+ =============================================================================
376
+ """.format(rmsbond=refmac_summary["cycles"][-1].get("rms_bond", "???"),
377
+ rmsangle=refmac_summary["cycles"][-1].get("rms_angle", "???"),
378
+ fscavgs=fscavg_text.rstrip(),
379
+ fsclog="{}_fsc.log".format(output_prefix),
380
+ adpstats=adpstats_txt.rstrip(),
381
+ final_weight=final_weight,
382
+ prefix=output_prefix,
383
+ map_peaks_msg=map_peaks_str))
384
+ # write_final_summary()
385
+
386
+ def lab_f_suffix(blur):
387
+ if blur is None or blur == 0.:
388
+ return ""
389
+ elif blur > 0:
390
+ return "Blur_{:.2f}".format(blur)
391
+ else:
392
+ return "Sharp_{:.2f}".format(-blur)
393
+ # lab_f_suffix()
394
+
395
+ def write_map_mtz(hkldata, mtz_out, map_labs, sig_lab=None, blur=0):
396
+ nblur = 2 if blur != 0 else 1
397
+ mean_f = hkldata.df[map_labs].abs().mean().min()
398
+ data_labs = map_labs + ([sig_lab] if sig_lab else [])
399
+
400
+ if mean_f < 1:
401
+ scale = 10. / mean_f
402
+ logger.writeln("Mean(|F|)= {:.2e} may be too small for Refmac. Applying scale= {:.1f}".format(mean_f, scale))
403
+ for lab in data_labs:
404
+ hkldata.df[lab] *= scale
405
+
406
+ mtz_labs = data_labs + []
407
+ mtz_types = {}
408
+ if sig_lab: mtz_types[sig_lab] = "Q"
409
+
410
+ if blur != 0:
411
+ temp = hkldata.debye_waller_factors(b_iso=blur)
412
+ for lab in data_labs:
413
+ data = hkldata.df[lab]
414
+ newlab = lab + lab_f_suffix(blur)
415
+ if numpy.iscomplexobj(data): data = numpy.abs(data)
416
+ hkldata.df[newlab] = data * temp
417
+ mtz_labs.append(newlab)
418
+ mtz_types[newlab] = "F" if lab != sig_lab else "Q"
419
+
420
+ hkldata.write_mtz(mtz_out, labs=mtz_labs, types=mtz_types,
421
+ phase_label_decorator=lambda x: "P"+x[1:])
422
+ # write_map_mtz()
423
+
424
+ def determine_b_before_mask(st, maps, grid_start, mask, resolution):
425
+ logger.writeln("Determining b_before_mask..")
426
+ # work in masked map for the speed
427
+ new_cell, new_shape, starts, shifts = shift_maps.determine_shape_and_shift(mask=mask,
428
+ grid_start=grid_start,
429
+ padding=5,
430
+ mask_cutoff=0.5,
431
+ noncentered=True,
432
+ noncubic=True,
433
+ json_out=None)
434
+ st = st.clone()
435
+ st.cell = new_cell
436
+ st.spacegroup_hm = "P 1"
437
+ for cra in st[0].all():
438
+ cra.atom.pos += shifts
439
+ cra.atom.b_iso = 0
440
+ cra.atom.aniso = gemmi.SMat33f(0,0,0,0,0,0)
441
+
442
+ newmaps = []
443
+ for i in range(len(maps)): # Update maps
444
+ g = gemmi.FloatGrid(maps[i][0].array*mask,
445
+ maps[i][0].unit_cell, maps[i][0].spacegroup)
446
+
447
+ suba = g.get_subarray(starts, new_shape)
448
+ new_grid = gemmi.FloatGrid(suba, new_cell, st.find_spacegroup())
449
+ newmaps.append([new_grid]+maps[i][1:])
450
+
451
+ hkldata = utils.maps.mask_and_fft_maps(newmaps, resolution)
452
+ hkldata.df["FC"] = utils.model.calc_fc_fft(st, resolution - 1e-6, source="electron",
453
+ miller_array=hkldata.miller_array())
454
+ k, b = hkldata.scale_k_and_b("FC", "FP")
455
+ return -b
456
+ # determine_b_before_mask()
457
+
458
+ def process_input(st, maps, resolution, monlib, mask_in, args,
459
+ shifted_model_prefix="shifted",
460
+ output_masked_prefix="masked_fs",
461
+ output_mtz_prefix="starting_map",
462
+ use_gemmi_prep=False, no_refmac_fix=False,
463
+ use_refmac=True, find_links=False):
464
+ ret = {} # instructions for refinement
465
+ maps = utils.maps.copy_maps(maps) # not to modify maps
466
+
467
+ grid_start = maps[0][1]
468
+ unit_cell = maps[0][0].unit_cell
469
+ spacegroup = gemmi.SpaceGroup(1)
470
+ start_xyz = numpy.array(maps[0][0].get_position(*grid_start).tolist())
471
+ A = unit_cell.orth.mat.array
472
+ center = numpy.sum(A, axis=1) / 2 #+ start_xyz
473
+
474
+ # Create mask
475
+ if mask_in:
476
+ logger.writeln("Input mask file: {}".format(mask_in))
477
+ mask = utils.fileio.read_ccp4_map(mask_in)[0]
478
+ else:
479
+ mask = None
480
+
481
+ st.cell = unit_cell
482
+ st.spacegroup_hm = "P 1"
483
+ if use_refmac:
484
+ ret["model_format"] = ".mmcif" if st.input_format == gemmi.CoorFormat.Mmcif else ".pdb"
485
+ max_seq_num = max([max(res.seqid.num for res in chain) for model in st for chain in model])
486
+ if max_seq_num > 9999 and ret["model_format"] == ".pdb":
487
+ logger.writeln("Max residue number ({}) exceeds 9999. Will use mmcif format".format(max_seq_num))
488
+ ret["model_format"] = ".mmcif"
489
+
490
+ if len(st.ncs) > 0 and args.ignore_symmetry:
491
+ logger.writeln("Removing symmetry information from model.")
492
+ st.ncs.clear()
493
+ utils.symmetry.update_ncs_from_args(args, st, map_and_start=maps[0], filter_contacting=args.contacting_only)
494
+ st_expanded = st.clone()
495
+ if not all(op.given for op in st.ncs):
496
+ # symmetry is not exact in helical reconstructions
497
+ cc_cutoff = 0.9 if args.twist is None else 0.7
498
+ if not args.no_check_ncs_overlaps and utils.model.check_symmetry_related_model_duplication(st):
499
+ raise SystemExit("\nError: Too many symmetry-related contacts detected.\n"
500
+ "Please provide an asymmetric unit model along with symmetry operators.")
501
+ if not args.no_check_ncs_map and utils.maps.check_symmetry_related_map_values(st, maps[0][0], cc_cutoff=cc_cutoff):
502
+ raise SystemExit("\nError: Map correlation is too small. Please ensure your map follows the model's symmetry")
503
+ args.keywords.extend(utils.symmetry.ncs_ops_for_refmac(st.ncs))
504
+ utils.model.expand_ncs(st_expanded)
505
+ logger.writeln(" Saving expanded model: input_model_expanded.*")
506
+ utils.fileio.write_model(st_expanded, "input_model_expanded", pdb=True, cif=True)
507
+
508
+ if mask is not None and not args.no_check_mask_with_model:
509
+ if not utils.maps.test_mask_with_model(mask, st_expanded):
510
+ raise SystemExit("\nError: Model is out of mask.\n"
511
+ "Please check your --model and --mask. You can disable this test with --no_check_mask_with_model.")
512
+ if args.mask_for_fofc:
513
+ masktmp = utils.fileio.read_ccp4_map(args.mask_for_fofc)[0]
514
+ if masktmp.shape != maps[0][0].shape:
515
+ raise SystemExit("\nError: mask from --mask_for_fofc has a different shape from input map(s)")
516
+ if not args.no_check_mask_with_model and not utils.maps.test_mask_with_model(masktmp, st):
517
+ raise SystemExit("\nError: Model is out of mask.\n"
518
+ "Please check your --model and --mask_for_fofc. You can disable this test with --no_check_mask_with_model.")
519
+ del masktmp
520
+
521
+ if mask is None and args.mask_radius:
522
+ logger.writeln("Creating mask..")
523
+ mask = utils.maps.mask_from_model(st_expanded, args.mask_radius, soft_edge=args.mask_soft_edge, grid=maps[0][0])
524
+ #utils.maps.write_ccp4_map("mask_from_model.ccp4", mask)
525
+
526
+ if use_refmac:
527
+ logger.writeln(" Saving input model with unit cell information")
528
+ utils.fileio.write_model(st, "starting_model", pdb=True, cif=True)
529
+ ret["model_file"] = "starting_model" + ret["model_format"]
530
+
531
+ if mask is not None:
532
+ if args.invert_mask:
533
+ logger.writeln("Inverting mask..")
534
+ mask_max, mask_min = numpy.max(mask), numpy.min(mask)
535
+ logger.writeln(" mask_max, mask_min= {}, {}".format(mask_max, mask_min))
536
+ mask = mask_max + mask_min - mask
537
+
538
+ # Mask maps
539
+ if args.no_sharpen_before_mask or len(maps) < 2:
540
+ logger.writeln("Applying mask..")
541
+ for ma in maps: ma[0].array[:] *= mask
542
+ else:
543
+ logger.writeln("Sharpen-mask-unsharpen..")
544
+ b_before_mask = args.b_before_mask
545
+ if b_before_mask is None: b_before_mask = determine_b_before_mask(st, maps, grid_start, mask, resolution)
546
+ maps = utils.maps.sharpen_mask_unsharpen(maps, mask, resolution, b=b_before_mask)
547
+
548
+ if not args.no_trim:
549
+ logger.writeln(" Shifting maps and/or model..")
550
+ if args.padding is None: args.padding = args.mask_radius * 2
551
+ new_cell, new_shape, starts, shifts = shift_maps.determine_shape_and_shift(mask=mask,
552
+ grid_start=grid_start,
553
+ padding=args.padding,
554
+ mask_cutoff=0.5,
555
+ noncentered=True,
556
+ noncubic=True,
557
+ json_out=None)
558
+ ret["shifts"] = shifts
559
+ vol_mask = numpy.count_nonzero(mask.array>0.5)
560
+ vol_map = new_shape[0] * new_shape[1] * new_shape[2]
561
+ ret["vol_ratio"] = vol_mask / vol_map
562
+ logger.writeln(" Vol_mask/Vol_map= {:.2e}".format(ret["vol_ratio"]))
563
+
564
+ # Model may be built out of the box (with 'unit cell' translation symmetry)
565
+ # It is only valid with original unit cell, but no longer valid with the new cell
566
+ # It still would not work if model is built over multiple 'cells'.
567
+ extra_shift = utils.model.translate_into_box(st,
568
+ origin=gemmi.Position(*start_xyz),
569
+ apply_shift=False)
570
+ if numpy.linalg.norm(extra_shift) > 0:
571
+ logger.writeln("Input model is out of the box. Required shift= {}".format(extra_shift))
572
+ ret["shifts"] += gemmi.Position(*extra_shift)
573
+ logger.writeln("Shift for model has been adjusted: {}".format(numpy.array(ret["shifts"].tolist())))
574
+
575
+ st.cell = new_cell
576
+ st.spacegroup_hm = "P 1"
577
+ if use_refmac:
578
+ logger.writeln(" Saving model in trimmed map..")
579
+ utils.fileio.write_model(st, shifted_model_prefix, pdb=True, cif=True)
580
+ ret["model_file"] = shifted_model_prefix + ret["model_format"]
581
+
582
+ logger.writeln(" Trimming maps..")
583
+ for i in range(len(maps)): # Update maps
584
+ suba = maps[i][0].get_subarray(starts, new_shape)
585
+ new_grid = gemmi.FloatGrid(suba, new_cell, spacegroup)
586
+ maps[i][0] = new_grid
587
+
588
+ st.setup_cell_images()
589
+ utils.restraints.find_and_fix_links(st, monlib, add_found=find_links,
590
+ # link via ncsc is not supported as of Refmac5.8.0411
591
+ find_symmetry_related=not use_refmac)
592
+ # workaround for Refmac
593
+ # TODO need to check external restraints
594
+ if use_refmac:
595
+ if use_gemmi_prep:
596
+ h_change = {"all":gemmi.HydrogenChange.ReAddButWater,
597
+ "yes":gemmi.HydrogenChange.NoChange,
598
+ "no":gemmi.HydrogenChange.Remove}[args.hydrogen]
599
+ topo, metal_kws = utils.restraints.prepare_topology(st, monlib, h_change=h_change, raise_error=False)
600
+ args.keywords = metal_kws + args.keywords
601
+ elif not no_refmac_fix:
602
+ topo = gemmi.prepare_topology(st, monlib, warnings=logger.silent(), ignore_unknown_links=True)
603
+ else:
604
+ topo = None # not used
605
+ if not no_refmac_fix:
606
+ ret["refmac_fixes"] = utils.refmac.FixForRefmac()
607
+ ret["refmac_fixes"].fix_before_topology(st, topo,
608
+ fix_microheterogeneity=not args.no_fix_microheterogeneity and not use_gemmi_prep,
609
+ fix_resimax=not args.no_fix_resi9999,
610
+ fix_nonpolymer=False)
611
+ chain_id_len_max = max([len(x) for x in utils.model.all_chain_ids(st)])
612
+ if chain_id_len_max > 1 and ret["model_format"] == ".pdb":
613
+ logger.writeln("Long chain ID (length: {}) detected. Will use mmcif format".format(chain_id_len_max))
614
+ ret["model_format"] = ".mmcif"
615
+ if not no_refmac_fix and ret["model_format"] == ".mmcif" and not use_gemmi_prep:
616
+ ret["refmac_fixes"].fix_nonpolymer(st)
617
+
618
+ if use_refmac and use_gemmi_prep:
619
+ # TODO: make cispept, make link, remove unknown link id
620
+ # TODO: cross validation?
621
+ crdout = os.path.splitext(ret["model_file"])[0] + ".crd"
622
+ ret["model_file"] = crdout
623
+ ret["model_format"] = ".mmcif"
624
+ args.keywords.append("make cr prepared")
625
+ gemmi.setup_for_crd(st)
626
+ doc = gemmi.prepare_refmac_crd(st, topo, monlib, h_change)
627
+ doc.write_file(crdout, options=gemmi.cif.Style.NoBlankLines)
628
+ logger.writeln("crd file written: {}".format(crdout))
629
+
630
+ hkldata = utils.maps.mask_and_fft_maps(maps, resolution, None, with_000=False)
631
+ hkldata.setup_relion_binning("ml")
632
+ hkldata.copy_binning(src="ml", dst="stat") # todo test usual binning for ml
633
+ if len(maps) == 2:
634
+ map_labs = ["Fmap1", "Fmap2", "Fout"]
635
+ ret["lab_f_half1"] = "Fmap1" + lab_f_suffix(args.blur)
636
+ # TODO Add SIGF in case of half maps, when refmac is ready
637
+ ret["lab_phi_half1"] = "Pmap1"
638
+ ret["lab_f_half2"] = "Fmap2" + lab_f_suffix(args.blur)
639
+ ret["lab_phi_half2"] = "Pmap2"
640
+ utils.maps.calc_noise_var_from_halfmaps(hkldata)
641
+ d_eff_full = hkldata.d_eff("ml", "FSCfull")
642
+ logger.writeln("Effective resolution from FSCfull= {:.2f}".format(d_eff_full))
643
+ ret["d_eff"] = d_eff_full
644
+ else:
645
+ map_labs = ["Fout"]
646
+ sig_lab = None
647
+
648
+ if use_refmac:
649
+ if args.no_mask:
650
+ logger.writeln("Saving unmasked maps as mtz file..")
651
+ mtzout = output_mtz_prefix+".mtz"
652
+ else:
653
+ logger.writeln(" Saving masked maps as mtz file..")
654
+ mtzout = output_masked_prefix+"_obs.mtz"
655
+
656
+ hkldata.df.rename(columns=dict(F_map1="Fmap1", F_map2="Fmap2", FP="Fout"), inplace=True)
657
+ if "shifts" in ret:
658
+ for lab in map_labs: # apply phase shift
659
+ logger.writeln(" applying phase shift for {} with translation {}".format(lab, -ret["shifts"]))
660
+ hkldata.translate(lab, -ret["shifts"])
661
+
662
+ write_map_mtz(hkldata, mtzout, map_labs=map_labs, blur=args.blur)
663
+ ret["mtz_file"] = mtzout
664
+ ret["lab_f"] = "Fout" + lab_f_suffix(args.blur)
665
+ ret["lab_phi"] = "Pout"
666
+ else:
667
+ fac = hkldata.debye_waller_factors(b_iso=args.blur)
668
+ if "shifts" in ret: fac *= hkldata.translation_factor(-ret["shifts"])
669
+ for lab in ("F_map1", "F_map2", "FP"):
670
+ if lab in hkldata.df: hkldata.df[lab] *= fac
671
+ return hkldata, ret
672
+ # process_input()
673
+
674
+ def check_args(args):
675
+ if not os.path.exists(args.model):
676
+ raise SystemExit("Error: --model {} does not exist.".format(args.model))
677
+
678
+ if args.cross_validation and not args.halfmaps:
679
+ raise SystemExit("Error: half maps are needed when --cross_validation is given")
680
+
681
+ if args.mask_for_fofc and not os.path.exists(args.mask_for_fofc):
682
+ raise SystemExit("Error: --mask_for_fofc {} does not exist".format(args.mask_for_fofc))
683
+
684
+ if args.mask_for_fofc and args.mask_radius_for_fofc:
685
+ raise SystemExit("Error: you cannot specify both --mask_for_fofc and --mask_radius_for_fofc")
686
+
687
+ if args.trim_fofc_mtz and not (args.mask_for_fofc or args.mask_radius_for_fofc):
688
+ raise SystemExit("Error: --trim_fofc_mtz is specified but --mask_for_fofc is not given")
689
+
690
+ if args.ligand: args.ligand = sum(args.ligand, [])
691
+
692
+ if args.keywords:
693
+ args.keywords = sum(args.keywords, [])
694
+ else:
695
+ args.keywords = []
696
+
697
+ if args.keyword_file:
698
+ args.keyword_file = sum(args.keyword_file, [])
699
+ for f in args.keyword_file:
700
+ if not os.path.exists(f):
701
+ raise SystemExit(f"Error: keyword file was not found: {f}")
702
+ logger.writeln("Keyword file: {}".format(f))
703
+ else:
704
+ args.keyword_file = []
705
+
706
+ if (args.twist, args.rise).count(None) == 1:
707
+ raise SystemExit("ERROR: give both helical parameters --twist and --rise")
708
+ if args.twist is not None:
709
+ logger.writeln("INFO: setting --contacting_only because helical symmetry is given")
710
+ args.contacting_only = True
711
+ if args.no_mask:
712
+ args.mask_radius = None
713
+ if not args.no_trim:
714
+ logger.writeln("WARNING: setting --no_trim because --no_mask is given")
715
+ args.no_trim = True
716
+ if args.mask:
717
+ logger.writeln("WARNING: Your --mask is ignored because --no_mask is given")
718
+ args.mask = None
719
+
720
+ #if args.mask_soft_edge > 0:
721
+ # logger.writeln("INFO: --mask_soft_edge={} is given. Turning off sharpen_before_mask.".format(args.mask_soft_edge))
722
+ # args.no_sharpen_before_mask = True
723
+
724
+ if args.resolution is None and args.model and utils.fileio.splitext(args.model)[1].endswith("cif"):
725
+ doc = gemmi.cif.read(args.model)
726
+ if len(doc) != 1:
727
+ raise SystemExit("cannot find resolution from cif. Give --resolution")
728
+ block = doc.sole_block()
729
+ reso_str = block.find_value("_em_3d_reconstruction.resolution")
730
+ try:
731
+ args.resolution = float(reso_str)
732
+ except:
733
+ raise SystemExit("ERROR: _em_3d_reconstruction.resolution is invalid. Give --resolution")
734
+ logger.writeln("WARNING: --resolution not given. Using _em_3d_reconstruction.resolution = {}".format(reso_str))
735
+
736
+ if args.resolution is None:
737
+ raise SystemExit("ERROR: --resolution is needed.")
738
+ # check_args()
739
+
740
+ def main(args):
741
+ check_args(args)
742
+ use_gemmi_prep = False
743
+ if not args.prepare_only:
744
+ refmac_ver = utils.refmac.check_version(args.exe)
745
+ if not refmac_ver:
746
+ raise SystemExit("Error: Check Refmac installation or use --exe to give the location.")
747
+ if not args.no_refmacat and refmac_ver >= (5, 8, 404):
748
+ logger.writeln(" will use gemmi to prepare restraints")
749
+ use_gemmi_prep = True
750
+ else:
751
+ logger.writeln(" will use makecif to prepare restraints")
752
+
753
+ logger.writeln("Input model: {}".format(args.model))
754
+ st = utils.fileio.read_structure(args.model)
755
+ if len(st) > 1:
756
+ logger.writeln(" Removing models 2-{}".format(len(st)))
757
+ for i in reversed(range(1, len(st))):
758
+ del st[i]
759
+
760
+ try:
761
+ monlib = utils.restraints.load_monomer_library(st, monomer_dir=args.monlib, cif_files=args.ligand,
762
+ stop_for_unknowns=True)
763
+ except RuntimeError as e:
764
+ raise SystemExit("Error: {}".format(e))
765
+
766
+ utils.model.setup_entities(st, clear=True, force_subchain_names=True, overwrite_entity_type=True)
767
+ try:
768
+ utils.restraints.prepare_topology(st.clone(), monlib, h_change=gemmi.HydrogenChange.NoChange,
769
+ check_hydrogen=(args.hydrogen=="yes"))
770
+ except RuntimeError as e:
771
+ raise SystemExit("Error: {}".format(e))
772
+
773
+ if args.halfmaps:
774
+ maps = utils.fileio.read_halfmaps(args.halfmaps, pixel_size=args.pixel_size)
775
+ else:
776
+ maps = [utils.fileio.read_ccp4_map(args.map, pixel_size=args.pixel_size)]
777
+
778
+ utils.model.remove_charge([st])
779
+ shifted_model_prefix = "shifted"
780
+ _, file_info = process_input(st, maps, resolution=args.resolution - 1e-6, monlib=monlib,
781
+ mask_in=args.mask, args=args,
782
+ shifted_model_prefix=shifted_model_prefix,
783
+ use_gemmi_prep=use_gemmi_prep,
784
+ find_links=args.find_links)
785
+ if args.prepare_only:
786
+ logger.writeln("\n--prepare_only is given. Stopping.")
787
+ return
788
+
789
+ args.mtz = file_info["mtz_file"]
790
+ if args.halfmaps: # FIXME if no_mask?
791
+ args.mtz_half = [file_info["mtz_file"], file_info["mtz_file"]]
792
+ args.lab_phi = file_info["lab_phi"] #"Pout0"
793
+ args.lab_f = file_info["lab_f"]
794
+ args.lab_sigf = None
795
+ args.model = file_info["model_file"] # refmac xyzin
796
+ model_format = file_info["model_format"]
797
+
798
+ if args.cross_validation and args.cross_validation_method == "throughout":
799
+ args.lab_f = file_info["lab_f_half1"]
800
+ args.lab_phi = file_info["lab_phi_half1"]
801
+ # XXX args.lab_sigf?
802
+
803
+ chain_id_lens = [len(x) for x in utils.model.all_chain_ids(st)]
804
+ keep_chain_ids = (chain_id_lens and max(chain_id_lens) == 1) # always kept unless one-letter chain IDs
805
+
806
+ # FIXME if mtz is given and sfcalc() not ran?
807
+
808
+ if not args.no_trim:
809
+ refmac_prefix = "{}_{}".format(shifted_model_prefix, args.output_prefix)
810
+ else:
811
+ refmac_prefix = args.output_prefix # XXX this should be different name (nomask etc?)
812
+
813
+ # Weight auto scale
814
+ if args.weight_auto_scale is None:
815
+ reso = file_info["d_eff"] if "d_eff" in file_info else args.resolution
816
+ if "vol_ratio" in file_info:
817
+ if "d_eff" in file_info:
818
+ rlmc = (-9.503541, 3.129882, 15.439744)
819
+ else:
820
+ rlmc = (-8.329418, 3.032409, 14.381907)
821
+ logger.writeln("Estimating weight auto scale using resolution and volume ratio")
822
+ ws = rlmc[0] + reso*rlmc[1] +file_info["vol_ratio"]*rlmc[2]
823
+ else:
824
+ if "d_eff" in file_info:
825
+ rlmc = (-5.903807, 2.870723)
826
+ else:
827
+ rlmc = (-4.891140, 2.746791)
828
+ logger.writeln("Estimating weight auto scale using resolution")
829
+ ws = rlmc[0] + args.resolution*rlmc[1]
830
+ args.weight_auto_scale = max(0.2, min(18.0, ws))
831
+ logger.writeln(" Will use weight auto {:.2f}".format(args.weight_auto_scale))
832
+
833
+ # Run Refmac
834
+ refmac = utils.refmac.Refmac(prefix=refmac_prefix, args=args, global_mode="spa",
835
+ keep_chain_ids=keep_chain_ids)
836
+ refmac.set_libin(args.ligand)
837
+ try:
838
+ refmac_summary = refmac.run_refmac()
839
+ except RuntimeError as e:
840
+ raise SystemExit("Error: {}".format(e))
841
+
842
+ # Modify output
843
+ st, cif_ref = utils.fileio.read_structure_from_pdb_and_mmcif(refmac_prefix+model_format)
844
+ utils.model.setup_entities(st, clear=True, overwrite_entity_type=True, force_subchain_names=True)
845
+
846
+ if not args.no_trim:
847
+ st.cell = maps[0][0].unit_cell
848
+ st.setup_cell_images()
849
+ if "refmac_fixes" in file_info:
850
+ file_info["refmac_fixes"].modify_back(st)
851
+ utils.model.adp_analysis(st)
852
+ utils.fileio.write_model(st, prefix=args.output_prefix,
853
+ pdb=True, cif=True, cif_ref=cif_ref)
854
+
855
+ # Take care of TLS out
856
+ if not args.no_trim: # if no_trim, there is nothing to do.
857
+ tlsout = refmac.tlsout()
858
+ if os.path.exists(tlsout):
859
+ logger.writeln("Copying tlsout")
860
+ shutil.copyfile(refmac.tlsout(), args.output_prefix+".tls")
861
+
862
+ # Expand sym here
863
+ st_expanded = st.clone()
864
+ if not all(op.given for op in st.ncs):
865
+ utils.model.expand_ncs(st_expanded)
866
+ utils.fileio.write_model(st_expanded, args.output_prefix+"_expanded", pdb=True, cif=True,
867
+ cif_ref=cif_ref)
868
+
869
+ if args.cross_validation and args.cross_validation_method == "shake":
870
+ logger.writeln("Cross validation is requested.")
871
+ refmac_prefix_shaken = refmac_prefix+"_shaken_refined"
872
+ logger.writeln("Starting refinement using half map 1 (model is shaken first)")
873
+ logger.writeln("In this refinement, hydrogen is removed regardless of --hydrogen option")
874
+ if use_gemmi_prep:
875
+ xyzin = refmac_prefix + ".crd"
876
+ st_tmp = utils.fileio.read_structure(refmac_prefix+model_format)
877
+ utils.model.setup_entities(st_tmp, clear=True, overwrite_entity_type=True, force_subchain_names=True)
878
+ prepare_crd(st_tmp,
879
+ crdout=xyzin, ligand=[refmac_prefix+model_format],
880
+ make={"hydr":"n"},
881
+ fix_long_resnames=False) # we do not need output file - do we?
882
+ else:
883
+ xyzin = refmac_prefix + model_format
884
+ refmac_hm1 = refmac.copy(hklin=args.mtz_half[0],
885
+ xyzin=xyzin,
886
+ prefix=refmac_prefix_shaken,
887
+ shake=args.shake_radius,
888
+ jellybody=False, # makes no sense to use jelly body after shaking
889
+ hydrogen="no") # should not use hydrogen after shaking
890
+ if args.jellybody: logger.writeln(" Turning off jelly body")
891
+ if "lab_f_half1" in file_info:
892
+ refmac_hm1.lab_f = file_info["lab_f_half1"]
893
+ refmac_hm1.lab_phi = file_info["lab_phi_half1"]
894
+ # SIGMA?
895
+
896
+ try:
897
+ refmac_hm1.run_refmac()
898
+ except RuntimeError as e:
899
+ raise SystemExit("Error: {}".format(e))
900
+
901
+ if args.hydrogen != "no": # does not work properly when 'yes' - we would need to keep hydrogen in input
902
+ logger.writeln("Cross validation: 2nd run with hydrogen")
903
+ if use_gemmi_prep:
904
+ xyzin = refmac_prefix_shaken + ".crd"
905
+ st_tmp = utils.fileio.read_structure(refmac_prefix_shaken+model_format)
906
+ utils.model.setup_entities(st_tmp, clear=True, overwrite_entity_type=True, force_subchain_names=True)
907
+ prepare_crd(st_tmp,
908
+ crdout=xyzin, ligand=[refmac_prefix+model_format],
909
+ make={"hydr":"a"},
910
+ fix_long_resnames=False) # we do not need output file - do we?
911
+ else:
912
+ xyzin = refmac_prefix_shaken + model_format
913
+ refmac_prefix_shaken = refmac_prefix+"_shaken_refined2"
914
+ refmac_hm1_2 = refmac_hm1.copy(xyzin=xyzin,
915
+ prefix=refmac_prefix_shaken,
916
+ shake=None,
917
+ hydrogen="all")
918
+ try:
919
+ refmac_hm1_2.run_refmac()
920
+ except RuntimeError as e:
921
+ raise SystemExit("Error: {}".format(e))
922
+
923
+ # Modify output
924
+ st_sr, cif_ref_sr = utils.fileio.read_structure_from_pdb_and_mmcif(refmac_prefix_shaken+model_format)
925
+ utils.model.setup_entities(st_sr, clear=True, overwrite_entity_type=True, force_subchain_names=True)
926
+ if not args.no_trim:
927
+ st_sr.cell = maps[0][0].unit_cell
928
+ st_sr.setup_cell_images()
929
+ if "refmac_fixes" in file_info:
930
+ file_info["refmac_fixes"].modify_back(st_sr)
931
+
932
+ utils.fileio.write_model(st_sr, prefix=args.output_prefix+"_shaken_refined",
933
+ pdb=True, cif=True, cif_ref=cif_ref_sr)
934
+
935
+ # Expand sym here
936
+ st_sr_expanded = st_sr.clone()
937
+ if not all(op.given for op in st_sr.ncs):
938
+ utils.model.expand_ncs(st_sr_expanded)
939
+ utils.fileio.write_model(st_sr_expanded, args.output_prefix+"_shaken_refined_expanded",
940
+ pdb=True, cif=True, cif_ref=cif_ref_sr)
941
+ if args.twist is not None: # as requested by a user
942
+ st_sr_expanded_all = st_sr.clone()
943
+ utils.symmetry.update_ncs_from_args(args, st_sr_expanded_all, map_and_start=maps[0], filter_contacting=False)
944
+ utils.model.expand_ncs(st_sr_expanded_all)
945
+ utils.fileio.write_model(st_sr_expanded_all, args.output_prefix+"_shaken_refined_expanded_all", pdb=True, cif=True,
946
+ cif_ref=cif_ref)
947
+ else:
948
+ st_sr_expanded = None
949
+
950
+ if args.mask:
951
+ mask = utils.fileio.read_ccp4_map(args.mask)[0]
952
+ else:
953
+ mask = None
954
+
955
+ # Calc FSC
956
+ fscavg_text = calc_fsc(st_expanded, args.output_prefix, maps,
957
+ args.resolution, mask=mask, mask_radius=args.mask_radius if not args.no_mask else None,
958
+ soft_edge=args.mask_soft_edge,
959
+ b_before_mask=args.b_before_mask,
960
+ no_sharpen_before_mask=args.no_sharpen_before_mask,
961
+ make_hydrogen=args.hydrogen,
962
+ monlib=monlib, cross_validation=args.cross_validation,
963
+ blur=args.blur,
964
+ d_min_fsc=args.fsc_resolution,
965
+ cross_validation_method=args.cross_validation_method, st_sr=st_sr_expanded)[0]
966
+
967
+ # Calc Fo-Fc (and updated) maps
968
+ calc_fofc(st, st_expanded, maps, monlib, model_format, args)
969
+
970
+ # Final summary
971
+ write_final_summary(st, refmac_summary, fscavg_text, args.output_prefix,
972
+ args.mask_for_fofc or args.mask_radius_for_fofc)
973
+ # main()
974
+
975
+ if __name__ == "__main__":
976
+ import sys
977
+ args = parse_args(sys.argv[1:])
978
+ main(args)
979
+