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,245 @@
|
|
|
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 argparse
|
|
10
|
+
import os
|
|
11
|
+
import gemmi
|
|
12
|
+
import numpy
|
|
13
|
+
import json
|
|
14
|
+
import servalcat # for version
|
|
15
|
+
from servalcat.utils import logger
|
|
16
|
+
from servalcat import utils
|
|
17
|
+
from servalcat.refine.refine import RefineParams, Geom, Refine, convert_stats_to_dicts, update_meta, print_h_options, load_config
|
|
18
|
+
from servalcat.refmac import refmac_keywords
|
|
19
|
+
|
|
20
|
+
def add_arguments(parser):
|
|
21
|
+
group = parser.add_mutually_exclusive_group(required=True)
|
|
22
|
+
group.add_argument('--model',
|
|
23
|
+
help='Input atomic model file')
|
|
24
|
+
group.add_argument('--update_dictionary',
|
|
25
|
+
help="Dictionary file to be updated")
|
|
26
|
+
parser.add_argument("--monlib",
|
|
27
|
+
help="Monomer library path. Default: $CLIBD_MON")
|
|
28
|
+
parser.add_argument('--ligand', nargs="*", action="append",
|
|
29
|
+
help="restraint dictionary cif file(s)")
|
|
30
|
+
parser.add_argument('--ncycle', type=int, default=10,
|
|
31
|
+
help="(when --model is used) number of CG cycles (default: %(default)d)")
|
|
32
|
+
parser.add_argument('--ncycle_update', type=int, nargs=2, default=[10,30], metavar=("NCYCLE_1", "NCYCLE_2"),
|
|
33
|
+
help="(when --update_dictionary is used) number of cycles for the first and second steps (default: %(default)s)")
|
|
34
|
+
parser.add_argument('--hydrogen', default="all", choices=["all", "yes", "no"],
|
|
35
|
+
help="all: add riding hydrogen atoms, yes: use hydrogen atoms if present, no: remove hydrogen atoms in input. "
|
|
36
|
+
"Default: %(default)s")
|
|
37
|
+
parser.add_argument('--find_links', action='store_true',
|
|
38
|
+
help='Automatically add links')
|
|
39
|
+
parser.add_argument('--randomize', type=float, default=0,
|
|
40
|
+
help='Shake coordinates with specified rmsd')
|
|
41
|
+
parser.add_argument('--ncsr', action='store_true',
|
|
42
|
+
help='Use local NCS restraints')
|
|
43
|
+
parser.add_argument('--keywords', nargs='+', action="append",
|
|
44
|
+
help="refmac keyword(s)")
|
|
45
|
+
parser.add_argument('--keyword_file', nargs='+', action="append",
|
|
46
|
+
help="refmac keyword file(s)")
|
|
47
|
+
parser.add_argument('-o','--output_prefix',
|
|
48
|
+
help="Output prefix")
|
|
49
|
+
parser.add_argument("--write_trajectory", action='store_true',
|
|
50
|
+
help="Write all output from cycles")
|
|
51
|
+
parser.add_argument("--config",
|
|
52
|
+
help="Config file (.yaml)")
|
|
53
|
+
parser.add_argument('--adp', choices=["fix"], default="fix", help=argparse.SUPPRESS) # dummy for omegaconf
|
|
54
|
+
# add_arguments()
|
|
55
|
+
|
|
56
|
+
def parse_args(arg_list):
|
|
57
|
+
parser = argparse.ArgumentParser()
|
|
58
|
+
add_arguments(parser)
|
|
59
|
+
return parser.parse_args(arg_list)
|
|
60
|
+
# parse_args()
|
|
61
|
+
|
|
62
|
+
def add_program_info_to_dictionary(block, comp_id, program_name="servalcat", descriptor="optimization tool"):
|
|
63
|
+
# old acedrg used _pdbx_chem_comp_description_generator. and descriptor
|
|
64
|
+
# new acedrg (>280?) uses _acedrg_chem_comp_descriptor. and type
|
|
65
|
+
for tag, name in (("_acedrg_chem_comp_descriptor.", "type"),
|
|
66
|
+
("_pdbx_chem_comp_description_generator.", "descriptor")):
|
|
67
|
+
tab = block.find(tag, ["program_name", "program_version", name])
|
|
68
|
+
if tab:
|
|
69
|
+
loop = tab.loop
|
|
70
|
+
# just overwrite version if it's there
|
|
71
|
+
for row in tab:
|
|
72
|
+
if row.str(0) == program_name and row.str(2) == descriptor:
|
|
73
|
+
row[1] = gemmi.cif.quote(servalcat.__version__)
|
|
74
|
+
return
|
|
75
|
+
break
|
|
76
|
+
else:
|
|
77
|
+
# it may be strange to say _acedrg in this case..
|
|
78
|
+
name = "type"
|
|
79
|
+
loop = block.init_loop("_acedrg_chem_comp_descriptor.", ["comp_id",
|
|
80
|
+
"program_name",
|
|
81
|
+
"program_version",
|
|
82
|
+
name])
|
|
83
|
+
tags = [x[x.index(".")+1:] for x in loop.tags]
|
|
84
|
+
row = ["" for _ in range(len(tags))]
|
|
85
|
+
for tag, val in (("comp_id", comp_id),
|
|
86
|
+
("program_name", program_name),
|
|
87
|
+
("program_version", servalcat.__version__),
|
|
88
|
+
(name, descriptor)):
|
|
89
|
+
if tag in tags: row[tags.index(tag)] = val
|
|
90
|
+
loop.add_row(gemmi.cif.quote_list(row))
|
|
91
|
+
# add_program_info_to_dictionary()
|
|
92
|
+
|
|
93
|
+
def refine_and_update_dictionary(cif_in, monomer_dir, output_prefix, refine_cfg, randomize=0, ncycle1=10, ncycle2=30):
|
|
94
|
+
doc = gemmi.cif.read(cif_in)
|
|
95
|
+
for block in doc: # this block will be reused below
|
|
96
|
+
st = gemmi.make_structure_from_chemcomp_block(block)
|
|
97
|
+
if len(st) > 0: break
|
|
98
|
+
else:
|
|
99
|
+
raise SystemExit("No model in the cif file")
|
|
100
|
+
for i in range(len(st)-1):
|
|
101
|
+
del st[1]
|
|
102
|
+
try:
|
|
103
|
+
monlib = utils.restraints.load_monomer_library(st, monomer_dir=monomer_dir, # monlib is needed for ener_lib
|
|
104
|
+
cif_files=[cif_in],
|
|
105
|
+
stop_for_unknowns=True)
|
|
106
|
+
except RuntimeError as e:
|
|
107
|
+
raise SystemExit("Error: {}".format(e))
|
|
108
|
+
all_stats = []
|
|
109
|
+
for i_macro in 0, 1:
|
|
110
|
+
try:
|
|
111
|
+
topo, _ = utils.restraints.prepare_topology(st, monlib, h_change=[gemmi.HydrogenChange.Remove, gemmi.HydrogenChange.ReAdd][i_macro],
|
|
112
|
+
check_hydrogen=(i_macro == 1))
|
|
113
|
+
except RuntimeError as e:
|
|
114
|
+
raise SystemExit("Error: {}".format(e))
|
|
115
|
+
|
|
116
|
+
refine_params = RefineParams(st, refine_xyz=True)
|
|
117
|
+
geom = Geom(st, topo, monlib, refine_params, shake_rms=randomize)
|
|
118
|
+
refiner = Refine(st, geom, refine_cfg, refine_params)
|
|
119
|
+
logger.writeln("Running {} cycles with wchir=4 wvdw=2 {} hydrogen".format(ncycle1, ["without","with"][i_macro]))
|
|
120
|
+
geom.calc_kwds["wchir"] = 4
|
|
121
|
+
geom.calc_kwds["wvdw"] = 2
|
|
122
|
+
all_stats.append(refiner.run_cycles(ncycle1))
|
|
123
|
+
|
|
124
|
+
logger.writeln("Running {} cycles with wchir=1 wvdw=2 {} hydrogen".format(ncycle2, ["without","with"][i_macro]))
|
|
125
|
+
geom.calc_kwds["wchir"] = 1
|
|
126
|
+
geom.calc_kwds["wvdw"] = 2
|
|
127
|
+
all_stats.append(refiner.run_cycles(ncycle2))
|
|
128
|
+
|
|
129
|
+
# replace xyz
|
|
130
|
+
pos = {cra.atom.name: cra.atom.pos.tolist() for cra in refiner.st[0].all()}
|
|
131
|
+
for row in block.find("_chem_comp_atom.", ["atom_id", "?x", "?y", "?z",
|
|
132
|
+
"?pdbx_model_Cartn_x_ideal",
|
|
133
|
+
"?pdbx_model_Cartn_y_ideal",
|
|
134
|
+
"?pdbx_model_Cartn_z_ideal",
|
|
135
|
+
"?model_Cartn_x", "?model_Cartn_y", "?model_Cartn_z"]):
|
|
136
|
+
p = pos[row.str(0)]
|
|
137
|
+
for i in range(3):
|
|
138
|
+
if row.has(i+1):
|
|
139
|
+
row[i+1] = "{:.3f}".format(p[i])
|
|
140
|
+
if row.has(i+4):
|
|
141
|
+
row[i+4] = "{:.3f}".format(p[i])
|
|
142
|
+
if row.has(i+7):
|
|
143
|
+
row[i+7] = "{:.3f}".format(p[i])
|
|
144
|
+
# add description
|
|
145
|
+
add_program_info_to_dictionary(block, st[0][0][0].name)
|
|
146
|
+
doc.write_file(output_prefix + "_updated.cif", options=gemmi.cif.Style.Aligned)
|
|
147
|
+
logger.writeln("Updated dictionary saved: {}".format(output_prefix + "_updated.cif"))
|
|
148
|
+
with open(output_prefix + "_stats.json", "w") as ofs:
|
|
149
|
+
json.dump([convert_stats_to_dicts(x) for x in all_stats],
|
|
150
|
+
ofs, indent=2)
|
|
151
|
+
logger.writeln("Refinement statistics saved: {}".format(ofs.name))
|
|
152
|
+
# refine_and_update_dictionary()
|
|
153
|
+
|
|
154
|
+
def refine_geom(model_in, monomer_dir, cif_files, h_change, ncycle, output_prefix, randomize, params,
|
|
155
|
+
refine_cfg, find_links=False, use_ncsr=False):
|
|
156
|
+
st = utils.fileio.read_structure(model_in)
|
|
157
|
+
utils.model.setup_entities(st, clear=True, force_subchain_names=True, overwrite_entity_type=True,
|
|
158
|
+
fix_sequences=True)
|
|
159
|
+
if not all(op.given for op in st.ncs):
|
|
160
|
+
st2 = st.clone()
|
|
161
|
+
logger.writeln("Take NCS constraints into account.")
|
|
162
|
+
st2.expand_ncs(gemmi.HowToNameCopiedChain.Dup, merge_dist=0)
|
|
163
|
+
utils.fileio.write_model(st2, file_name="input_expanded.pdb")
|
|
164
|
+
try:
|
|
165
|
+
monlib = utils.restraints.load_monomer_library(st, monomer_dir=monomer_dir,
|
|
166
|
+
cif_files=cif_files,
|
|
167
|
+
stop_for_unknowns=True,
|
|
168
|
+
params=params)
|
|
169
|
+
except RuntimeError as e:
|
|
170
|
+
raise SystemExit("Error: {}".format(e))
|
|
171
|
+
utils.restraints.find_and_fix_links(st, monlib, find_metal_links=find_links,
|
|
172
|
+
add_found=find_links) # should remove unknown id here?
|
|
173
|
+
try:
|
|
174
|
+
topo, _ = utils.restraints.prepare_topology(st, monlib, h_change=h_change,
|
|
175
|
+
check_hydrogen=(h_change==gemmi.HydrogenChange.NoChange),
|
|
176
|
+
params=params)
|
|
177
|
+
except RuntimeError as e:
|
|
178
|
+
raise SystemExit("Error: {}".format(e))
|
|
179
|
+
|
|
180
|
+
print_h_options(h_change, st[0].has_hydrogen(), refine_h=True, hout=True, geom_only=True)
|
|
181
|
+
|
|
182
|
+
if use_ncsr:
|
|
183
|
+
ncslist = utils.restraints.prepare_ncs_restraints(st)
|
|
184
|
+
else:
|
|
185
|
+
ncslist = False
|
|
186
|
+
refine_params = RefineParams(st, refine_xyz=True, cfg=refine_cfg)
|
|
187
|
+
geom = Geom(st, topo, monlib, refine_params, shake_rms=randomize, params=params, ncslist=ncslist)
|
|
188
|
+
refiner = Refine(st, geom, refine_cfg, refine_params)
|
|
189
|
+
stats = refiner.run_cycles(ncycle,
|
|
190
|
+
stats_json_out=output_prefix + "_stats.json")
|
|
191
|
+
update_meta(st, stats[-1])
|
|
192
|
+
refiner.st.name = output_prefix
|
|
193
|
+
utils.fileio.write_model(refiner.st, output_prefix, pdb=True, cif=True)
|
|
194
|
+
if refine_cfg.write_trajectory:
|
|
195
|
+
utils.fileio.write_model(refiner.st_traj, output_prefix + "_traj", cif=True)
|
|
196
|
+
# refine_geom()
|
|
197
|
+
|
|
198
|
+
def main(args):
|
|
199
|
+
keywords = []
|
|
200
|
+
if args.keywords: keywords = sum(args.keywords, [])
|
|
201
|
+
if args.keyword_file: keywords.extend(l for f in sum(args.keyword_file, []) for l in open(f))
|
|
202
|
+
params = refmac_keywords.parse_keywords(keywords)
|
|
203
|
+
refine_cfg = load_config(args.config, args, params)
|
|
204
|
+
decide_prefix = lambda f: utils.fileio.splitext(os.path.basename(f))[0] + "_refined"
|
|
205
|
+
if args.model:
|
|
206
|
+
if not args.output_prefix:
|
|
207
|
+
args.output_prefix = decide_prefix(args.model)
|
|
208
|
+
if args.ligand:
|
|
209
|
+
args.ligand = sum(args.ligand, [])
|
|
210
|
+
h_change = {"all":gemmi.HydrogenChange.ReAddButWater,
|
|
211
|
+
"full":gemmi.HydrogenChange.ReAdd,
|
|
212
|
+
"yes":gemmi.HydrogenChange.NoChange,
|
|
213
|
+
"no":gemmi.HydrogenChange.Remove}[args.hydrogen]
|
|
214
|
+
refine_geom(model_in=args.model,
|
|
215
|
+
monomer_dir=args.monlib,
|
|
216
|
+
cif_files=args.ligand,
|
|
217
|
+
h_change=h_change,
|
|
218
|
+
ncycle=args.ncycle,
|
|
219
|
+
output_prefix=args.output_prefix,
|
|
220
|
+
randomize=args.randomize,
|
|
221
|
+
params=params,
|
|
222
|
+
refine_cfg=refine_cfg,
|
|
223
|
+
find_links=args.find_links,
|
|
224
|
+
use_ncsr=args.ncsr)
|
|
225
|
+
else:
|
|
226
|
+
if not args.output_prefix:
|
|
227
|
+
args.output_prefix = decide_prefix(args.update_dictionary)
|
|
228
|
+
if args.ligand:
|
|
229
|
+
logger.writeln("WARNING: monlib and ligand are ignored in the dictionary updating mode")
|
|
230
|
+
if keywords:
|
|
231
|
+
logger.writeln("WARNING: refmac keywords are ignored in the dictionary updating mode")
|
|
232
|
+
refine_and_update_dictionary(cif_in=args.update_dictionary,
|
|
233
|
+
monomer_dir=args.monlib,
|
|
234
|
+
output_prefix=args.output_prefix,
|
|
235
|
+
refine_cfg=refine_cfg,
|
|
236
|
+
randomize=args.randomize,
|
|
237
|
+
ncycle1=args.ncycle_update[0],
|
|
238
|
+
ncycle2=args.ncycle_update[1])
|
|
239
|
+
|
|
240
|
+
# main()
|
|
241
|
+
|
|
242
|
+
if __name__ == "__main__":
|
|
243
|
+
import sys
|
|
244
|
+
args = parse_args(sys.argv[1:])
|
|
245
|
+
main(args)
|
|
@@ -0,0 +1,400 @@
|
|
|
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 argparse
|
|
11
|
+
import numpy
|
|
12
|
+
from servalcat.utils import logger
|
|
13
|
+
from servalcat import utils
|
|
14
|
+
from servalcat.spa.run_refmac import check_args, process_input, calc_fsc, calc_fofc
|
|
15
|
+
from servalcat.spa import fofc
|
|
16
|
+
from servalcat.refine import spa
|
|
17
|
+
from servalcat.refine.refine import Geom, Refine, RefineParams, update_meta, print_h_options, load_config
|
|
18
|
+
from servalcat.refmac import refmac_keywords
|
|
19
|
+
b_to_u = utils.model.b_to_u
|
|
20
|
+
|
|
21
|
+
def add_arguments(parser):
|
|
22
|
+
parser.description = "program to refine cryo-EM SPA structures"
|
|
23
|
+
group = parser.add_mutually_exclusive_group(required=True)
|
|
24
|
+
group.add_argument("--halfmaps", nargs=2, help="Input half map files")
|
|
25
|
+
group.add_argument("--map", help="Use this only if you really do not have half maps.")
|
|
26
|
+
group.add_argument("--hklin", help="Use mtz file. With limited functionality.")
|
|
27
|
+
parser.add_argument('--pixel_size', type=float,
|
|
28
|
+
help='Override pixel size (A)')
|
|
29
|
+
parser.add_argument('--labin',
|
|
30
|
+
help='F,PHI for hklin')
|
|
31
|
+
parser.add_argument('--model', required=True,
|
|
32
|
+
help='Input atomic model file')
|
|
33
|
+
parser.add_argument("-d", '--resolution', type=float, required=True)
|
|
34
|
+
parser.add_argument('-r', '--mask_radius', type=float, default=3, help="mask radius")
|
|
35
|
+
parser.add_argument('--padding',
|
|
36
|
+
type=float,
|
|
37
|
+
help='Default: 2*mask_radius')
|
|
38
|
+
parser.add_argument('--no_mask', action='store_true')
|
|
39
|
+
parser.add_argument('--no_trim',
|
|
40
|
+
action='store_true',
|
|
41
|
+
help='Keep original box (not recommended)')
|
|
42
|
+
parser.add_argument('--mask_soft_edge',
|
|
43
|
+
type=float, default=0,
|
|
44
|
+
help='Add soft edge to model mask. Should use with --no_sharpen_before_mask?')
|
|
45
|
+
parser.add_argument('--no_sharpen_before_mask', action='store_true',
|
|
46
|
+
help='By default half maps are sharpened before masking by std of signal and unsharpened after masking. This option disables it.')
|
|
47
|
+
parser.add_argument("--b_before_mask", type=float,
|
|
48
|
+
help="sharpening B value for sharpen-mask-unsharpen procedure. By default it is determined automatically.")
|
|
49
|
+
parser.add_argument('--blur',
|
|
50
|
+
type=float, default=0,
|
|
51
|
+
help='Sharpening or blurring B')
|
|
52
|
+
parser.add_argument("--monlib",
|
|
53
|
+
help="Monomer library path. Default: $CLIBD_MON")
|
|
54
|
+
parser.add_argument('--ligand', nargs="*", action="append",
|
|
55
|
+
help="restraint dictionary cif file(s)")
|
|
56
|
+
parser.add_argument('--newligand_continue', action='store_true',
|
|
57
|
+
help="Make ad-hoc restraints for unknown ligands (not recommended)")
|
|
58
|
+
parser.add_argument('--hydrogen', default="all", choices=["all", "yes", "no"],
|
|
59
|
+
help="all: (re)generate hydrogen atoms, yes: use hydrogen atoms if present, no: remove hydrogen atoms in input. "
|
|
60
|
+
"Default: %(default)s")
|
|
61
|
+
parser.add_argument('--hout', action='store_true', help="write hydrogen atoms in the output model")
|
|
62
|
+
parser.add_argument('--jellybody', action='store_true',
|
|
63
|
+
help="Use jelly body restraints")
|
|
64
|
+
parser.add_argument('--jellybody_params', nargs=2, type=float,
|
|
65
|
+
metavar=("sigma", "dmax"), default=[0.01, 4.2],
|
|
66
|
+
help="Jelly body sigma and dmax (default: %(default)s)")
|
|
67
|
+
parser.add_argument('--jellyonly', action='store_true',
|
|
68
|
+
help="Jelly body only (experimental, may not be useful)")
|
|
69
|
+
utils.symmetry.add_symmetry_args(parser) # add --pg etc
|
|
70
|
+
parser.add_argument('--contacting_only', action="store_true", help="Filter out non-contacting strict NCS copies")
|
|
71
|
+
parser.add_argument('--ignore_symmetry', action='store_true',
|
|
72
|
+
help='Ignore symmetry information (MTRIX/_struct_ncs_oper) in the model file')
|
|
73
|
+
parser.add_argument('--find_links', action='store_true',
|
|
74
|
+
help='Automatically add links')
|
|
75
|
+
parser.add_argument('--no_check_ncs_overlaps', action='store_true',
|
|
76
|
+
help='Disable model overlap test due to strict NCS')
|
|
77
|
+
parser.add_argument('--no_check_ncs_map', action='store_true',
|
|
78
|
+
help='Disable map symmetry test due to strict NCS')
|
|
79
|
+
parser.add_argument('--no_check_mask_with_model', action='store_true',
|
|
80
|
+
help='Disable mask test using model')
|
|
81
|
+
parser.add_argument('--keywords', nargs='+', action="append",
|
|
82
|
+
help="refmac keyword(s)")
|
|
83
|
+
parser.add_argument('--keyword_file', nargs='+', action="append",
|
|
84
|
+
help="refmac keyword file(s)")
|
|
85
|
+
parser.add_argument('--randomize', type=float, default=0,
|
|
86
|
+
help='Shake coordinates with the specified rmsd value')
|
|
87
|
+
parser.add_argument('--ncycle', type=int, default=10,
|
|
88
|
+
help="number of CG cycles (default: %(default)d)")
|
|
89
|
+
parser.add_argument('--weight', type=float,
|
|
90
|
+
help="refinement weight. default: automatic")
|
|
91
|
+
parser.add_argument('--no_weight_adjust', action='store_true',
|
|
92
|
+
help='Do not adjust weight during refinement')
|
|
93
|
+
parser.add_argument('--target_bond_rmsz_range', nargs=2, type=float, default=[0.5, 1.],
|
|
94
|
+
help='Bond rmsz range for weight adjustment (default: %(default)s)')
|
|
95
|
+
parser.add_argument('--adpr_weight', type=float, default=1.,
|
|
96
|
+
help="ADP restraint weight (default: %(default)f)")
|
|
97
|
+
parser.add_argument('--occr_weight', type=float, default=0.,
|
|
98
|
+
help="Occupancy restraint weight (default: %(default)f)")
|
|
99
|
+
parser.add_argument('--ncsr', action='store_true',
|
|
100
|
+
help='Use local NCS restraints')
|
|
101
|
+
parser.add_argument('--bfactor', type=float,
|
|
102
|
+
help="reset all atomic B values to the specified value")
|
|
103
|
+
parser.add_argument('--fix_xyz', action="store_true",
|
|
104
|
+
help="Fix atomic coordinates")
|
|
105
|
+
parser.add_argument('--adp', choices=["fix", "iso", "aniso"], default="iso",
|
|
106
|
+
help="ADP parameterization")
|
|
107
|
+
parser.add_argument('--refine_all_occ', action="store_true")
|
|
108
|
+
parser.add_argument('--max_dist_for_adp_restraint', type=float, default=4.)
|
|
109
|
+
parser.add_argument('--adp_restraint_power', type=float)
|
|
110
|
+
parser.add_argument('--adp_restraint_exp_fac', type=float)
|
|
111
|
+
parser.add_argument('--adp_restraint_no_long_range', action='store_true')
|
|
112
|
+
parser.add_argument('--adp_restraint_mode', choices=["diff", "kldiv"], default="diff")
|
|
113
|
+
parser.add_argument('--unrestrained', action='store_true', help="No positional restraints")
|
|
114
|
+
parser.add_argument('--refine_h', action="store_true", help="Refine hydrogen against data (default: only restraints apply)")
|
|
115
|
+
parser.add_argument("-s", "--source", choices=["electron", "xray", "neutron", "custom"], default="electron")
|
|
116
|
+
parser.add_argument('-o','--output_prefix', default="refined")
|
|
117
|
+
parser.add_argument('--cross_validation', action='store_true',
|
|
118
|
+
help='Run cross validation. Only "throughout" mode is available (no "shake" mode)')
|
|
119
|
+
group = parser.add_mutually_exclusive_group()
|
|
120
|
+
group.add_argument('--mask_for_fofc', help="Mask file for Fo-Fc map calculation")
|
|
121
|
+
group.add_argument('--mask_radius_for_fofc', type=float, help="Mask radius for Fo-Fc map calculation")
|
|
122
|
+
parser.add_argument("--fsc_resolution", type=float,
|
|
123
|
+
help="High resolution limit for FSC calculation. Default: Nyquist")
|
|
124
|
+
parser.add_argument('--keep_charges', action='store_true',
|
|
125
|
+
help="Use scattering factor for charged atoms. Use it with care.")
|
|
126
|
+
parser.add_argument("--keep_entities", action='store_true',
|
|
127
|
+
help="Do not override entities")
|
|
128
|
+
parser.add_argument("--write_trajectory", action='store_true',
|
|
129
|
+
help="Write all output from cycles")
|
|
130
|
+
parser.add_argument("--config",
|
|
131
|
+
help="Config file (.yaml)")
|
|
132
|
+
parser.add_argument("--halfmapcc_for_dynamic_weighting", help=argparse.SUPPRESS) # testing
|
|
133
|
+
# add_arguments()
|
|
134
|
+
|
|
135
|
+
def parse_args(arg_list):
|
|
136
|
+
parser = argparse.ArgumentParser()
|
|
137
|
+
add_arguments(parser)
|
|
138
|
+
return parser.parse_args(arg_list)
|
|
139
|
+
# parse_args()
|
|
140
|
+
|
|
141
|
+
def main(args):
|
|
142
|
+
args.mask = None
|
|
143
|
+
args.invert_mask = False
|
|
144
|
+
args.trim_fofc_mtz = args.mask_for_fofc is not None
|
|
145
|
+
args.cross_validation_method = "throughout"
|
|
146
|
+
check_args(args)
|
|
147
|
+
params = refmac_keywords.parse_keywords(args.keywords + [l for f in args.keyword_file for l in open(f)])
|
|
148
|
+
refine_cfg = load_config(args.config, args, params)
|
|
149
|
+
|
|
150
|
+
st = utils.fileio.read_structure(args.model)
|
|
151
|
+
ccu = utils.model.CustomCoefUtil()
|
|
152
|
+
if args.source == "custom":
|
|
153
|
+
ccu.read_from_cif(st, args.model)
|
|
154
|
+
if args.unrestrained:
|
|
155
|
+
monlib = gemmi.MonLib()
|
|
156
|
+
topo = None
|
|
157
|
+
if args.hydrogen == "all":
|
|
158
|
+
logger.writeln("\nWARNING: in unrestrained refinement hydrogen atoms are not generated.\n")
|
|
159
|
+
if args.hydrogen != "yes":
|
|
160
|
+
args.hydrogen = "no"
|
|
161
|
+
st.remove_hydrogens()
|
|
162
|
+
for i, cra in enumerate(st[0].all()):
|
|
163
|
+
cra.atom.serial = i + 1
|
|
164
|
+
else:
|
|
165
|
+
try:
|
|
166
|
+
monlib = utils.restraints.load_monomer_library(st, monomer_dir=args.monlib, cif_files=args.ligand,
|
|
167
|
+
stop_for_unknowns=not args.newligand_continue,
|
|
168
|
+
params=params)
|
|
169
|
+
except RuntimeError as e:
|
|
170
|
+
raise SystemExit("Error: {}".format(e))
|
|
171
|
+
if not args.keep_entities:
|
|
172
|
+
utils.model.setup_entities(st, clear=True, force_subchain_names=True, overwrite_entity_type=True,
|
|
173
|
+
fix_sequences=True)
|
|
174
|
+
if not args.keep_charges:
|
|
175
|
+
utils.model.remove_charge([st])
|
|
176
|
+
if args.source == "custom":
|
|
177
|
+
ccu.show_info()
|
|
178
|
+
else:
|
|
179
|
+
utils.model.check_atomsf([st], args.source)
|
|
180
|
+
if args.hklin:
|
|
181
|
+
assert not args.cross_validation
|
|
182
|
+
mtz = utils.fileio.read_mmhkl(args.hklin)
|
|
183
|
+
hkldata = utils.hkl.hkldata_from_mtz(mtz, args.labin.split(","),
|
|
184
|
+
newlabels=["FP", ""],
|
|
185
|
+
require_types=["F", "P"])
|
|
186
|
+
hkldata.df = hkldata.df.dropna() # workaround for missing data
|
|
187
|
+
#hkldata.setup_relion_binning()
|
|
188
|
+
hkldata.setup_binning(n_bins=10, name="ml") # need to sort out
|
|
189
|
+
hkldata.copy_binning(src="ml", dst="stat")
|
|
190
|
+
st.cell = hkldata.cell
|
|
191
|
+
st.spacegroup_hm = hkldata.sg.xhm()
|
|
192
|
+
st.setup_cell_images()
|
|
193
|
+
info = {}
|
|
194
|
+
utils.restraints.find_and_fix_links(st, monlib, find_metal_links=args.find_links,
|
|
195
|
+
add_found=args.find_links)
|
|
196
|
+
else:
|
|
197
|
+
if args.halfmaps:
|
|
198
|
+
maps = utils.fileio.read_halfmaps(args.halfmaps, pixel_size=args.pixel_size)
|
|
199
|
+
else:
|
|
200
|
+
maps = [utils.fileio.read_ccp4_map(args.map, pixel_size=args.pixel_size)]
|
|
201
|
+
hkldata, info = process_input(st, maps, resolution=args.resolution - 1e-6, monlib=monlib,
|
|
202
|
+
mask_in=args.mask, args=args, use_refmac=False,
|
|
203
|
+
find_links=args.find_links)
|
|
204
|
+
h_change = {"all":gemmi.HydrogenChange.ReAddKnown,
|
|
205
|
+
"yes":gemmi.HydrogenChange.NoChange,
|
|
206
|
+
"no":gemmi.HydrogenChange.Remove}[args.hydrogen]
|
|
207
|
+
try:
|
|
208
|
+
topo, _ = utils.restraints.prepare_topology(st, monlib, h_change=h_change,
|
|
209
|
+
check_hydrogen=(args.hydrogen=="yes"),
|
|
210
|
+
params=params)
|
|
211
|
+
except RuntimeError as e:
|
|
212
|
+
raise SystemExit("Error: {}".format(e))
|
|
213
|
+
|
|
214
|
+
print_h_options(h_change, st[0].has_hydrogen(), args.refine_h, args.hout, geom_only=False)
|
|
215
|
+
|
|
216
|
+
# initialize values
|
|
217
|
+
utils.model.reset_adp(st[0], args.bfactor, args.adp)
|
|
218
|
+
utils.model.initialize_values(st[0], refine_cfg.initialisation)
|
|
219
|
+
|
|
220
|
+
# auto weight
|
|
221
|
+
if args.weight is None:
|
|
222
|
+
# from 230303_weight_test using 472 test cases
|
|
223
|
+
reso = info["d_eff"] if "d_eff" in info else args.resolution
|
|
224
|
+
if "vol_ratio" in info:
|
|
225
|
+
if "d_eff" in info:
|
|
226
|
+
rlmc = (-2.3976, 0.5933, 3.5160)
|
|
227
|
+
else:
|
|
228
|
+
rlmc = (-2.5151, 0.6681, 3.6467)
|
|
229
|
+
logger.writeln("Estimating weight auto scale using resolution and volume ratio")
|
|
230
|
+
ws = numpy.exp(rlmc[0] + reso*rlmc[1] +info["vol_ratio"]*rlmc[2])
|
|
231
|
+
else:
|
|
232
|
+
if "d_eff" in info:
|
|
233
|
+
rlmc = (-1.6908, 0.5668)
|
|
234
|
+
else:
|
|
235
|
+
rlmc = (-1.7588, 0.6311)
|
|
236
|
+
logger.writeln("Estimating weight auto scale using resolution")
|
|
237
|
+
ws = numpy.exp(rlmc[0] + args.resolution*rlmc[1])
|
|
238
|
+
args.weight = max(0.2, min(18.0, ws))
|
|
239
|
+
logger.writeln(" Will use weight= {:.2f}".format(args.weight))
|
|
240
|
+
|
|
241
|
+
if args.ncsr:
|
|
242
|
+
ncslist = utils.restraints.prepare_ncs_restraints(st)
|
|
243
|
+
else:
|
|
244
|
+
ncslist = False
|
|
245
|
+
refine_params = RefineParams(st, refine_xyz=not args.fix_xyz,
|
|
246
|
+
adp_mode=dict(fix=0, iso=1, aniso=2)[args.adp],
|
|
247
|
+
refine_occ=args.refine_all_occ,
|
|
248
|
+
refine_dfrac=False, cfg=refine_cfg,
|
|
249
|
+
exclude_h_ll=not args.refine_h)
|
|
250
|
+
if args.halfmapcc_for_dynamic_weighting:
|
|
251
|
+
localcc, _, _ = utils.fileio.read_ccp4_map(args.halfmapcc_for_dynamic_weighting, pixel_size=args.pixel_size)
|
|
252
|
+
max_w = 2
|
|
253
|
+
pos_all = numpy.array([a.pos.tolist() for a in refine_params.atoms])
|
|
254
|
+
cc = numpy.maximum(0, localcc.interpolate_position_array(pos_all))
|
|
255
|
+
cc_true = numpy.sqrt(2 * cc / (1 + cc))
|
|
256
|
+
w = max_w - 0.5 * (numpy.tanh(6 * cc_true - 3) + 1) * (max_w - 1)
|
|
257
|
+
refine_params.geom_weights[:] = w
|
|
258
|
+
# debug output
|
|
259
|
+
st_debug = st.clone()
|
|
260
|
+
for cra in st_debug[0].all():
|
|
261
|
+
i = cra.atom.serial - 1
|
|
262
|
+
cra.atom.b_iso = w[i]
|
|
263
|
+
cra.atom.occ = cc[i]
|
|
264
|
+
logger.writeln(f"{cra} {cc[i]} {w[i]}")
|
|
265
|
+
utils.fileio.write_model(st_debug, file_name="debug_weights.mmcif")
|
|
266
|
+
|
|
267
|
+
geom = Geom(st, topo, monlib, refine_params,
|
|
268
|
+
shake_rms=args.randomize, adpr_w=args.adpr_weight, occr_w=args.occr_weight,
|
|
269
|
+
params=params, unrestrained=args.unrestrained or args.jellyonly,
|
|
270
|
+
ncslist=ncslist)
|
|
271
|
+
if args.source == "custom":
|
|
272
|
+
ccu.set_coeffs(st)
|
|
273
|
+
ll = spa.LL_SPA(hkldata, st, monlib,
|
|
274
|
+
lab_obs="F_map1" if args.cross_validation else "FP",
|
|
275
|
+
source=args.source)
|
|
276
|
+
refiner = Refine(st, geom, refine_cfg, refine_params, ll,
|
|
277
|
+
unrestrained=args.unrestrained)
|
|
278
|
+
|
|
279
|
+
geom.geom.adpr_max_dist = args.max_dist_for_adp_restraint
|
|
280
|
+
if args.adp_restraint_power is not None: geom.geom.adpr_d_power = args.adp_restraint_power
|
|
281
|
+
if args.adp_restraint_exp_fac is not None: geom.geom.adpr_exp_fac = args.adp_restraint_exp_fac
|
|
282
|
+
if args.adp_restraint_no_long_range: geom.geom.adpr_long_range = False
|
|
283
|
+
geom.geom.adpr_mode = args.adp_restraint_mode
|
|
284
|
+
if args.jellybody or args.jellyonly: geom.geom.ridge_sigma, geom.geom.ridge_dmax = args.jellybody_params
|
|
285
|
+
if args.jellyonly: geom.geom.ridge_exclude_short_dist = False
|
|
286
|
+
|
|
287
|
+
#logger.writeln("TEST: shift x+0.3 A")
|
|
288
|
+
#for cra in st[0].all():
|
|
289
|
+
# cra.atom.pos += gemmi.Position(0.3,0,0)
|
|
290
|
+
|
|
291
|
+
stats = refiner.run_cycles(args.ncycle, weight=args.weight,
|
|
292
|
+
weight_adjust=not args.no_weight_adjust,
|
|
293
|
+
weight_adjust_bond_rmsz_range=args.target_bond_rmsz_range,
|
|
294
|
+
stats_json_out=args.output_prefix + "_stats.json")
|
|
295
|
+
if not args.hklin and not args.no_trim:
|
|
296
|
+
refiner.st.cell = maps[0][0].unit_cell
|
|
297
|
+
refiner.st.setup_cell_images()
|
|
298
|
+
|
|
299
|
+
if refine_cfg.write_trajectory:
|
|
300
|
+
utils.fileio.write_model(refiner.st_traj, args.output_prefix + "_traj", cif=True)
|
|
301
|
+
|
|
302
|
+
# Expand sym here
|
|
303
|
+
st_expanded = refiner.st.clone()
|
|
304
|
+
if not all(op.given for op in st.ncs):
|
|
305
|
+
utils.model.expand_ncs(st_expanded)
|
|
306
|
+
|
|
307
|
+
# Calc FSC
|
|
308
|
+
if args.hklin: # cannot update a mask
|
|
309
|
+
stats_for_meta = stats[-1]
|
|
310
|
+
else:
|
|
311
|
+
mask = utils.fileio.read_ccp4_map(args.mask)[0] if args.mask else None
|
|
312
|
+
fscavg_text, stats2 = calc_fsc(st_expanded, args.output_prefix, maps,
|
|
313
|
+
args.resolution, mask=mask, mask_radius=args.mask_radius if not args.no_mask else None,
|
|
314
|
+
soft_edge=args.mask_soft_edge,
|
|
315
|
+
b_before_mask=args.b_before_mask,
|
|
316
|
+
no_sharpen_before_mask=args.no_sharpen_before_mask,
|
|
317
|
+
make_hydrogen="yes", # no change needed in the model
|
|
318
|
+
monlib=monlib,
|
|
319
|
+
blur=args.blur,
|
|
320
|
+
d_min_fsc=args.fsc_resolution,
|
|
321
|
+
cross_validation=args.cross_validation,
|
|
322
|
+
cross_validation_method=args.cross_validation_method,
|
|
323
|
+
source=args.source
|
|
324
|
+
)
|
|
325
|
+
stats_for_meta = {"geom": stats[-1]["geom"], "data": stats2}
|
|
326
|
+
update_meta(refiner.st, stats_for_meta, ll)
|
|
327
|
+
refiner.st.name = args.output_prefix
|
|
328
|
+
utils.fileio.write_model(refiner.st, args.output_prefix, pdb=True, cif=True, hout=args.hout)
|
|
329
|
+
if not all(op.given for op in st.ncs): # to apply updated metadata
|
|
330
|
+
st_expanded = refiner.st.clone()
|
|
331
|
+
utils.model.expand_ncs(st_expanded)
|
|
332
|
+
utils.fileio.write_model(st_expanded, args.output_prefix+"_expanded", pdb=True, cif=True, hout=args.hout)
|
|
333
|
+
if args.hklin:
|
|
334
|
+
return
|
|
335
|
+
# Calc Fo-Fc (and updated) maps
|
|
336
|
+
calc_fofc(refiner.st, st_expanded, maps, monlib, ".mmcif", args, diffmap_prefix=args.output_prefix, source=args.source)
|
|
337
|
+
|
|
338
|
+
# Final summary
|
|
339
|
+
adpstats_txt = ""
|
|
340
|
+
adp_stats = utils.model.adp_stats_per_chain(refiner.st[0])
|
|
341
|
+
max_chain_len = max([len(x[0]) for x in adp_stats])
|
|
342
|
+
max_num_len = max([len(str(x[1])) for x in adp_stats])
|
|
343
|
+
for chain, natoms, qs in adp_stats:
|
|
344
|
+
adpstats_txt += " Chain {0:{1}s}".format(chain, max_chain_len) if chain!="*" else " {0:{1}s}".format("All", max_chain_len+6)
|
|
345
|
+
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])
|
|
346
|
+
|
|
347
|
+
if "geom" in stats[-1] and "Bond distances, non H" in stats[-1]["geom"]["summary"].index:
|
|
348
|
+
rmsbond = stats[-1]["geom"]["summary"]["r.m.s.d."]["Bond distances, non H"]
|
|
349
|
+
rmsangle = stats[-1]["geom"]["summary"]["r.m.s.d."]["Bond angles, non H"]
|
|
350
|
+
else:
|
|
351
|
+
rmsbond, rmsangle = numpy.nan, numpy.nan
|
|
352
|
+
if args.mask_for_fofc:
|
|
353
|
+
map_peaks_str = """\
|
|
354
|
+
List Fo-Fc map peaks in the ASU:
|
|
355
|
+
servalcat util map_peaks --map {prefix}_normalized_fofc.mrc --model {prefix}.pdb --abs_level 4.0 \
|
|
356
|
+
""".format(prefix=args.output_prefix)
|
|
357
|
+
else:
|
|
358
|
+
map_peaks_str = "WARNING: --mask_for_fofc was not given, so the Fo-Fc map was not normalized."
|
|
359
|
+
|
|
360
|
+
logger.writeln("""
|
|
361
|
+
=============================================================================
|
|
362
|
+
* Final Summary *
|
|
363
|
+
|
|
364
|
+
Rmsd from ideal
|
|
365
|
+
bond lengths: {rmsbond:.4f} A
|
|
366
|
+
bond angles: {rmsangle:.3f} deg
|
|
367
|
+
|
|
368
|
+
{fscavgs}
|
|
369
|
+
Run loggraph {fsclog} to see plots
|
|
370
|
+
|
|
371
|
+
ADP statistics
|
|
372
|
+
{adpstats}
|
|
373
|
+
|
|
374
|
+
Weight used: {final_weight:.3e}
|
|
375
|
+
If you want to change the weight, give larger (looser restraints)
|
|
376
|
+
or smaller (tighter) value to --weight=.
|
|
377
|
+
|
|
378
|
+
Open refined model and {prefix}_maps.mtz with COOT:
|
|
379
|
+
coot --script {prefix}_coot.py
|
|
380
|
+
|
|
381
|
+
Open refined model, map and difference map with ChimeraX/ISOLDE:
|
|
382
|
+
chimerax {prefix}_chimerax.cxc
|
|
383
|
+
|
|
384
|
+
{map_peaks_msg}
|
|
385
|
+
=============================================================================
|
|
386
|
+
""".format(rmsbond=rmsbond,
|
|
387
|
+
rmsangle=rmsangle,
|
|
388
|
+
fscavgs=fscavg_text.rstrip(),
|
|
389
|
+
fsclog="{}_fsc.log".format(args.output_prefix),
|
|
390
|
+
adpstats=adpstats_txt.rstrip(),
|
|
391
|
+
final_weight=args.weight,
|
|
392
|
+
prefix=args.output_prefix,
|
|
393
|
+
map_peaks_msg=map_peaks_str))
|
|
394
|
+
|
|
395
|
+
# main()
|
|
396
|
+
|
|
397
|
+
if __name__ == "__main__":
|
|
398
|
+
import sys
|
|
399
|
+
args = parse_args(sys.argv[1:])
|
|
400
|
+
main(args)
|