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.
- servalcat/__init__.py +10 -0
- servalcat/__main__.py +120 -0
- servalcat/ext.cpython-314t-x86_64-linux-gnu.so +0 -0
- servalcat/refine/__init__.py +0 -0
- servalcat/refine/cgsolve.py +100 -0
- servalcat/refine/refine.py +1162 -0
- servalcat/refine/refine_geom.py +245 -0
- servalcat/refine/refine_spa.py +400 -0
- servalcat/refine/refine_xtal.py +339 -0
- servalcat/refine/spa.py +151 -0
- servalcat/refine/xtal.py +312 -0
- servalcat/refmac/__init__.py +0 -0
- servalcat/refmac/exte.py +191 -0
- servalcat/refmac/refmac_keywords.py +660 -0
- servalcat/refmac/refmac_wrapper.py +423 -0
- servalcat/spa/__init__.py +0 -0
- servalcat/spa/fofc.py +488 -0
- servalcat/spa/fsc.py +391 -0
- servalcat/spa/localcc.py +197 -0
- servalcat/spa/realspcc_from_var.py +128 -0
- servalcat/spa/run_refmac.py +979 -0
- servalcat/spa/shift_maps.py +293 -0
- servalcat/spa/shiftback.py +137 -0
- servalcat/spa/translate.py +129 -0
- servalcat/utils/__init__.py +35 -0
- servalcat/utils/commands.py +1629 -0
- servalcat/utils/fileio.py +836 -0
- servalcat/utils/generate_operators.py +296 -0
- servalcat/utils/hkl.py +811 -0
- servalcat/utils/logger.py +140 -0
- servalcat/utils/maps.py +345 -0
- servalcat/utils/model.py +933 -0
- servalcat/utils/refmac.py +759 -0
- servalcat/utils/restraints.py +888 -0
- servalcat/utils/symmetry.py +298 -0
- servalcat/xtal/__init__.py +0 -0
- servalcat/xtal/french_wilson.py +262 -0
- servalcat/xtal/run_refmac_small.py +240 -0
- servalcat/xtal/sigmaa.py +1954 -0
- servalcat/xtal/twin.py +316 -0
- servalcat-0.4.131.dist-info/METADATA +60 -0
- servalcat-0.4.131.dist-info/RECORD +45 -0
- servalcat-0.4.131.dist-info/WHEEL +6 -0
- servalcat-0.4.131.dist-info/entry_points.txt +4 -0
- 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
|
+
|