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,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)